From 0a7a5f0866342bff536ca2d8ceefd19c4ae04489 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:00:09 +1300 Subject: [PATCH 01/18] Add swoole transport --- .../OpenTelemetry/Swoole/Transport.php | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php diff --git a/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php b/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php new file mode 100644 index 0000000..0274127 --- /dev/null +++ b/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php @@ -0,0 +1,197 @@ + Pre-computed base headers */ + private array $baseHeaders; + + /** @var array Pre-computed client settings */ + private array $settings; + + private Channel $pool; + private int $poolSize; + + private Atomic $shutdown; + + public function __construct( + string $endpoint, + private string $contentType = ContentTypes::PROTOBUF, + private array $headers = [], + private float $timeout = 10.0, + int $poolSize = 8, + int $socketBufferSize = 64 * 1024, // 64 KB + ) { + $parsed = parse_url($endpoint); + $this->ssl = ($parsed['scheme'] ?? 'http') === 'https'; + $this->host = $parsed['host'] ?? 'localhost'; + $this->port = $parsed['port'] ?? ($this->ssl ? 443 : 80); + $this->path = $parsed['path'] ?? '/'; + if (isset($parsed['query'])) { + $this->path .= '?' . $parsed['query']; + } + + $this->poolSize = $poolSize; + $this->shutdown = new Atomic(0); + $this->pool = new Channel($this->poolSize); + + $this->baseHeaders = [ + 'Content-Type' => $this->contentType, + 'Connection' => 'keep-alive', + ...$this->headers, + ]; + $this->settings = [ + 'timeout' => $this->timeout, + 'connect_timeout' => \min(1.0, $this->timeout), + 'read_timeout' => \min(1.0, $this->timeout), + 'write_timeout' => \min(1.0, $this->timeout), + 'keep_alive' => true, + 'open_tcp_nodelay' => true, + 'open_tcp_keepalive' => true, + 'tcp_keepidle' => 60, + 'tcp_keepinterval' => 5, + 'tcp_keepcount' => 3, + 'socket_buffer_size' => $socketBufferSize, + 'http_compression' => false, + ]; + } + + /** + * Get the content type used for requests. + * + * @return string + */ + public function contentType(): string + { + return $this->contentType; + } + + /** + * Acquire a client from the pool or create a new one. + */ + private function popClient(): Client + { + $client = $this->pool->pop(0); + if ($client === false) { + $client = $this->pool->pop(0.05); + } + if ($client instanceof Client) { + if ($client->connected) { + return $client; + } + $client->close(); + } + + $client = new Client($this->host, $this->port, $this->ssl); + $client->set($this->settings); + + return $client; + } + + /** + * Return a client to the pool or close it. + */ + private function putClient(Client $client, bool $forceClose = false): void + { + if ($this->shutdown->get() === 1 || $forceClose || !$client->connected) { + $client->close(); + return; + } + if (!$this->pool->push($client, 1.0)) { + $client->close(); + } + } + + public function send(string $payload, ?CancellationInterface $cancellation = null): FutureInterface + { + if ($this->shutdown->get() === 1) { + return new ErrorFuture(new Exception('Transport has been shut down')); + } + + $client = null; + $forceClose = false; + + try { + $client = $this->popClient(); + $headers = $this->baseHeaders; + $headers['Content-Length'] = \strlen($payload); + $client->setHeaders($headers); + $client->post($this->path, $payload); + $statusCode = $client->getStatusCode(); + + // Connection error (timeout, reset, etc.) + if ($statusCode < 0) { + $forceClose = true; + $errCode = $client->errCode; + $errMsg = \socket_strerror($errCode); + + return new ErrorFuture(new Exception("OTLP connection failed: $errMsg (code: $errCode)")); + } + + $body = $client->getBody(); + + if ($statusCode >= 200 && $statusCode < 300) { + return new CompletedFuture($body); + } + + // Server error may need fresh connection + if ($statusCode >= 500) { + $forceClose = true; + } + + return new ErrorFuture(new Exception("OTLP export failed with status $statusCode: $body")); + } catch (\Throwable $e) { + $forceClose = true; + + return new ErrorFuture($e); + } finally { + if ($client !== null) { + $this->putClient($client, $forceClose); + } + } + } + + public function shutdown(?CancellationInterface $cancellation = null): bool + { + $this->shutdown->set(1); + + // Drain and close all pooled connections + while (!$this->pool->isEmpty()) { + $client = $this->pool->pop(0.001); + if ($client instanceof Client) { + $client->close(); + } + } + $this->pool->close(); + + return true; + } + + public function forceFlush(?CancellationInterface $cancellation = null): bool + { + return true; + } +} From 32bbef7593995fae05fec946090f78eb6936ccaa Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:00:34 +1300 Subject: [PATCH 02/18] Support custom transport in open telemetry --- src/Telemetry/Adapter/OpenTelemetry.php | 150 ++++++++++++++++-------- 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/src/Telemetry/Adapter/OpenTelemetry.php b/src/Telemetry/Adapter/OpenTelemetry.php index 0405f7d..a674a91 100644 --- a/src/Telemetry/Adapter/OpenTelemetry.php +++ b/src/Telemetry/Adapter/OpenTelemetry.php @@ -12,6 +12,7 @@ use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Attribute\AttributesInterface; +use OpenTelemetry\SDK\Common\Export\TransportInterface; use OpenTelemetry\SDK\Metrics\Data\Temporality; use OpenTelemetry\SDK\Metrics\MeterProvider; use OpenTelemetry\SDK\Metrics\MetricExporterInterface; @@ -29,7 +30,9 @@ class OpenTelemetry implements Adapter { private MetricReaderInterface $reader; + private MeterInterface $meter; + private array $meterStorage = [ Counter::class => [], UpDownCounter::class => [], @@ -37,17 +40,39 @@ class OpenTelemetry implements Adapter Gauge::class => [], ]; - public function __construct(string $endpoint, string $serviceNamespace, string $serviceName, string $serviceInstanceId) - { - $exporter = $this->createExporter($endpoint); + /** + * @param string $endpoint + * @param string $serviceNamespace + * @param string $serviceName + * @param string $serviceInstanceId + * @param TransportInterface|null $transport + */ + public function __construct( + string $endpoint, + string $serviceNamespace, + string $serviceName, + string $serviceInstanceId, + protected ?TransportInterface $transport = null + ) { + if ($this->transport === null) { + $this->transport = (new OtlpHttpTransportFactory()) + ->create($endpoint, ContentTypes::PROTOBUF); + } + + $exporter = $this->createExporter($this->transport); + $attributes = Attributes::create([ 'service.namespace' => $serviceNamespace, 'service.name' => $serviceName, 'service.instance.id' => $serviceInstanceId, ]); + $this->meter = $this->initMeter($exporter, $attributes); } + /** + * Initialize Meter + */ protected function initMeter(MetricExporterInterface $exporter, AttributesInterface $attributes): MeterInterface { $this->reader = new ExportingReader($exporter); @@ -61,77 +86,110 @@ protected function initMeter(MetricExporterInterface $exporter, AttributesInterf return $meterProvider->getMeter('cloud'); } - protected function createExporter(string $endpoint): MetricExporterInterface + /** + * Create Metric Exporter + */ + protected function createExporter(TransportInterface $transport): MetricExporterInterface { - $transport = (new OtlpHttpTransportFactory())->create($endpoint, ContentTypes::PROTOBUF); return new MetricExporter($transport, Temporality::CUMULATIVE); } private function createMeter(string $type, string $name, callable $creator): mixed { - if (!isset($this->meterStorage[$type][$name])) { + if (! isset($this->meterStorage[$type][$name])) { $this->meterStorage[$type][$name] = $creator(); } return $this->meterStorage[$type][$name]; } + /** + * Create a Counter metric + */ public function createCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Counter { - $counter = $this->createMeter(Counter::class, $name, fn () => $this->meter->createCounter($name, $unit, $description, $advisory)); - return new class ($counter) extends Counter { - public function __construct(private CounterInterface $counter) - { - } - public function add(float|int $amount, iterable $attributes = []): void - { - $this->counter->add($amount, $attributes); - } - }; + return $this->createMeter(Counter::class, $name, function () use ($name, $unit, $description, $advisory) { + $counter = $this->meter->createCounter($name, $unit, $description, $advisory); + + return new class ($counter) extends Counter { + public function __construct(private CounterInterface $counter) + { + } + + public function add(float|int $amount, iterable $attributes = []): void + { + $this->counter->add($amount, $attributes); + } + }; + }); } + /** + * Create a Histogram metric + */ public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Histogram { - $histogram = $this->createMeter(Histogram::class, $name, fn () => $this->meter->createHistogram($name, $unit, $description, $advisory)); - return new class ($histogram) extends Histogram { - public function __construct(private HistogramInterface $histogram) - { - } - public function record(float|int $amount, iterable $attributes = []): void - { - $this->histogram->record($amount, $attributes); - } - }; + return $this->createMeter(Histogram::class, $name, function () use ($name, $unit, $description, $advisory) { + $histogram = $this->meter->createHistogram($name, $unit, $description, $advisory); + + return new class ($histogram) extends Histogram { + public function __construct(private HistogramInterface $histogram) + { + } + + public function record(float|int $amount, iterable $attributes = []): void + { + $this->histogram->record($amount, $attributes); + } + }; + }); } + /** + * Create a Gauge metric + */ public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Gauge { - $gauge = $this->createMeter(Gauge::class, $name, fn () => $this->meter->createGauge($name, $unit, $description, $advisory)); - return new class ($gauge) extends Gauge { - public function __construct(private GaugeInterface $gauge) - { - } - public function record(float|int $amount, iterable $attributes = []): void - { - $this->gauge->record($amount, $attributes); - } - }; + return $this->createMeter(Gauge::class, $name, function () use ($name, $unit, $description, $advisory) { + $gauge = $this->meter->createGauge($name, $unit, $description, $advisory); + + return new class ($gauge) extends Gauge { + public function __construct(private GaugeInterface $gauge) + { + } + + public function record(float|int $amount, iterable $attributes = []): void + { + $this->gauge->record($amount, $attributes); + } + }; + }); } + /** + * Create an UpDownCounter metric + */ public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounter { - $upDownCounter = $this->createMeter(UpDownCounter::class, $name, fn () => $this->meter->createUpDownCounter($name, $unit, $description, $advisory)); - return new class ($upDownCounter) extends UpDownCounter { - public function __construct(private UpDownCounterInterface $upDownCounter) - { - } - public function add(float|int $amount, iterable $attributes = []): void - { - $this->upDownCounter->add($amount, $attributes); - } - }; + return $this->createMeter(UpDownCounter::class, $name, function () use ($name, $unit, $description, $advisory) { + $upDownCounter = $this->meter->createUpDownCounter($name, $unit, $description, $advisory); + + return new class ($upDownCounter) extends UpDownCounter { + public function __construct(private UpDownCounterInterface $upDownCounter) + { + } + + public function add(float|int $amount, iterable $attributes = []): void + { + $this->upDownCounter->add($amount, $attributes); + } + }; + }); } + /** + * Collect and export metrics + */ public function collect(): bool { return $this->reader->collect(); From d2f070038c337d0d823dc24f2d8f095a56ed2020 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:00:55 +1300 Subject: [PATCH 03/18] Update deps --- composer.json | 26 +- composer.lock | 1488 +++++++++++++++++++++++++++---------------------- phpunit.xml | 17 +- 3 files changed, 857 insertions(+), 674 deletions(-) diff --git a/composer.json b/composer.json index 59b9dc0..b71d94c 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,11 @@ "Utopia\\Telemetry\\": "src/Telemetry" } }, + "autoload-dev": { + "psr-4": { + "Tests\\Telemetry\\": "tests/Telemetry" + } + }, "scripts": { "lint": "vendor/bin/pint --test", "format": "vendor/bin/pint", @@ -24,16 +29,21 @@ "ext-protobuf": "*", "ext-opentelemetry": "*", "php": ">=8.0", - "open-telemetry/sdk": "^1.1", - "symfony/http-client": "^7.1", - "nyholm/psr7": "^1.8", - "open-telemetry/exporter-otlp": "^1.1" + "nyholm/psr7": "1.*", + "open-telemetry/exporter-otlp": "1.*", + "open-telemetry/sdk": "1.*", + "symfony/http-client": "7.*" }, "require-dev": { - "phpunit/phpunit": "^9.5.25", - "laravel/pint": "^1.2", - "phpstan/phpstan": "^1.10", - "phpbench/phpbench": "^1.2" + "phpunit/phpunit": "11.*", + "laravel/pint": "1.*", + "phpstan/phpstan": "2.*", + "phpbench/phpbench": "1.*", + "swoole/ide-helper": "6.*" + }, + "suggest": { + "ext-swoole": "Required for the Swoole transport implementation", + "ext-sockets": "Required for the Swoole transport implementation" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index f6ba6c5..17926e0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "656715b7623d3d2bfb431be7547348db", + "content-hash": "0bc07196160002f85ff89cf37156e082", "packages": [ { "name": "brick/math", - "version": "0.12.3", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -64,20 +64,20 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -129,7 +129,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -139,33 +139,29 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "google/protobuf", - "version": "v4.30.1", + "version": "v4.33.2", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24" + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/f29ba8a30dfd940efb3a8a75dc44446539101f24", - "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -187,9 +183,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" }, - "time": "2025-03-13T21:08:17+00:00" + "time": "2025-12-05T22:12:22+00:00" }, { "name": "nyholm/psr7", @@ -337,20 +333,20 @@ }, { "name": "open-telemetry/api", - "version": "1.2.3", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1" + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", - "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", "shasum": "" }, "require": { - "open-telemetry/context": "^1.0", + "open-telemetry/context": "^1.4", "php": "^8.1", "psr/log": "^1.1|^2.0|^3.0", "symfony/polyfill-php82": "^1.26" @@ -366,7 +362,7 @@ ] }, "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.8.x-dev" } }, "autoload": { @@ -399,24 +395,24 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-03-05T21:42:54+00:00" + "time": "2025-10-19T10:49:48+00:00" }, { "name": "open-telemetry/context", - "version": "1.1.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3" + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/0cba875ea1953435f78aec7f1d75afa87bdbf7f3", - "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", "shasum": "" }, "require": { @@ -462,20 +458,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-08-21T00:29:20+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/exporter-otlp", - "version": "1.2.1", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "b7580440b7481a98da97aceabeb46e1b276c8747" + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/b7580440b7481a98da97aceabeb46e1b276c8747", - "reference": "b7580440b7481a98da97aceabeb46e1b276c8747", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/07b02bc71838463f6edcc78d3485c04b48fb263d", + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d", "shasum": "" }, "require": { @@ -522,24 +518,24 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-03-06T23:21:56+00:00" + "time": "2025-11-13T08:04:37+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.5.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" + "reference": "673af5b06545b513466081884b47ef15a536edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", + "reference": "673af5b06545b513466081884b47ef15a536edde", "shasum": "" }, "require": { @@ -589,27 +585,27 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-15T23:07:07+00:00" + "time": "2025-09-17T23:10:12+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.2.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0" + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "~1.0 || ~1.1", - "open-telemetry/context": "^1.0", + "open-telemetry/api": "^1.7", + "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", "php-http/discovery": "^1.14", @@ -621,7 +617,7 @@ "ramsey/uuid": "^3.0 || ^4.0", "symfony/polyfill-mbstring": "^1.23", "symfony/polyfill-php82": "^1.26", - "tbachert/spi": "^1.0.1" + "tbachert/spi": "^1.0.5" }, "suggest": { "ext-gmp": "To support unlimited number of synchronous metric readers", @@ -631,12 +627,19 @@ "type": "library", "extra": { "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -675,24 +678,24 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-29T21:40:28+00:00" + "time": "2025-11-25T10:59:15+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.30.0", + "version": "1.37.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a" + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", - "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", "shasum": "" }, "require": { @@ -736,7 +739,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-02-06T00:21:48+00:00" + "time": "2025-09-03T12:08:10+00:00" }, { "name": "php-http/discovery", @@ -1082,16 +1085,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -1152,27 +1155,26 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", - "ext-json": "*", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1180,26 +1182,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -1234,32 +1233,22 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -1272,7 +1261,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1297,7 +1286,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1313,20 +1302,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/http-client", - "version": "v7.2.4", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" + "reference": "26cc224ea7103dda90e9694d9e139a389092d007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007", + "reference": "26cc224ea7103dda90e9694d9e139a389092d007", "shasum": "" }, "require": { @@ -1334,10 +1323,12 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.4" }, @@ -1350,18 +1341,18 @@ "require-dev": { "amphp/http-client": "^4.2.1|^5.0", "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1392,7 +1383,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.4" + "source": "https://github.com/symfony/http-client/tree/v7.4.1" }, "funding": [ { @@ -1403,25 +1394,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-12-04T21:12:57+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -1434,7 +1429,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1470,7 +1465,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -1486,23 +1481,24 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -1550,7 +1546,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -1561,16 +1557,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", @@ -1626,7 +1626,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" }, "funding": [ { @@ -1637,6 +1637,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1644,18 +1648,98 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -1673,7 +1757,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1709,7 +1793,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -1720,25 +1804,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "tbachert/spi", - "version": "v1.0.2", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/Nevay/spi.git", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", "shasum": "" }, "require": { @@ -1756,7 +1844,7 @@ "extra": { "class": "Nevay\\SPI\\Composer\\Plugin", "branch-alias": { - "dev-main": "0.2.x-dev" + "dev-main": "1.0.x-dev" }, "plugin-optional": true }, @@ -1775,9 +1863,9 @@ ], "support": { "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.2" + "source": "https://github.com/Nevay/spi/tree/v1.0.5" }, - "time": "2024-10-04T16:36:12+00:00" + "time": "2025-06-29T15:42:06+00:00" } ], "packages-dev": [ @@ -1855,78 +1943,9 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, - { - "name": "doctrine/instantiator", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:23:10+00:00" - }, { "name": "doctrine/lexer", "version": "3.0.1", @@ -2006,16 +2025,16 @@ }, { "name": "laravel/pint", - "version": "v1.21.2", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "370772e7d9e9da087678a0edf2b11b6960e40558" + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558", - "reference": "370772e7d9e9da087678a0edf2b11b6960e40558", + "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", "shasum": "" }, "require": { @@ -2026,13 +2045,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.72.0", - "illuminate/view": "^11.44.2", - "larastan/larastan": "^3.2.0", - "laravel-zero/framework": "^11.36.1", + "friendsofphp/php-cs-fixer": "^3.90.0", + "illuminate/view": "^12.40.1", + "larastan/larastan": "^3.8.0", + "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3", - "pestphp/pest": "^2.36.0" + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ "builds/pint" @@ -2058,6 +2077,7 @@ "description": "An opinionated code formatter for PHP.", "homepage": "https://laravel.com", "keywords": [ + "dev", "format", "formatter", "lint", @@ -2068,20 +2088,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-03-14T22:31:42+00:00" + "time": "2025-11-25T21:15:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -2120,7 +2140,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -2128,20 +2148,20 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -2160,7 +2180,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -2184,9 +2204,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -2308,24 +2328,24 @@ }, { "name": "phpbench/container", - "version": "2.2.2", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/phpbench/container.git", - "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33" + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33", - "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33", + "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196", + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196", "shasum": "" }, "require": { "psr/container": "^1.0|^2.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0" + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", + "php-cs-fixer/shim": "^3.89", "phpstan/phpstan": "^0.12.52", "phpunit/phpunit": "^8" }, @@ -2353,22 +2373,22 @@ "description": "Simple, configurable, service container.", "support": { "issues": "https://github.com/phpbench/container/issues", - "source": "https://github.com/phpbench/container/tree/2.2.2" + "source": "https://github.com/phpbench/container/tree/2.2.3" }, - "time": "2023-10-30T13:38:26+00:00" + "time": "2025-11-06T09:05:13+00:00" }, { "name": "phpbench/phpbench", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b" + "reference": "b641dde59d969ea42eed70a39f9b51950bc96878" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", - "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878", + "reference": "b641dde59d969ea42eed70a39f9b51950bc96878", "shasum": "" }, "require": { @@ -2383,26 +2403,26 @@ "phpbench/container": "^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", - "symfony/console": "^6.1 || ^7.0", - "symfony/filesystem": "^6.1 || ^7.0", - "symfony/finder": "^6.1 || ^7.0", - "symfony/options-resolver": "^6.1 || ^7.0", - "symfony/process": "^6.1 || ^7.0", + "symfony/console": "^6.1 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.1 || ^7.0 || ^8.0", + "symfony/finder": "^6.1 || ^7.0 || ^8.0", + "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0", + "symfony/process": "^6.1 || ^7.0 || ^8.0", "webmozart/glob": "^4.6" }, "require-dev": { "dantleech/invoke": "^2.0", "ergebnis/composer-normalize": "^2.39", - "friendsofphp/php-cs-fixer": "^3.0", "jangregor/phpstan-prophecy": "^1.0", - "phpspec/prophecy": "dev-master", + "php-cs-fixer/shim": "^3.9", + "phpspec/prophecy": "^1.22", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^10.4 || ^11.0", "rector/rector": "^1.2", - "symfony/error-handler": "^6.1 || ^7.0", - "symfony/var-dumper": "^6.1 || ^7.0" + "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" }, "suggest": { "ext-xdebug": "For Xdebug profiling extension." @@ -2445,7 +2465,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.1" + "source": "https://github.com/phpbench/phpbench/tree/1.4.3" }, "funding": [ { @@ -2453,24 +2473,19 @@ "type": "github" } ], - "time": "2025-03-12T08:01:40+00:00" + "time": "2025-11-06T19:07:31+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.21", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "14276fdef70575106a3392a4ed553c06a984df28" - }, + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/14276fdef70575106a3392a4ed553c06a984df28", - "reference": "14276fdef70575106a3392a4ed553c06a984df28", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -2511,39 +2526,39 @@ "type": "github" } ], - "time": "2025-03-09T09:24:50+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^11.5.2" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -2552,7 +2567,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -2581,40 +2596,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2641,7 +2668,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -2649,28 +2677,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -2678,7 +2706,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2704,7 +2732,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -2712,32 +2741,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2763,7 +2792,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -2771,32 +2801,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -2822,7 +2852,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -2830,54 +2861,52 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "11.5.46", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -2885,7 +2914,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -2917,7 +2946,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" }, "funding": [ { @@ -2928,12 +2957,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-12-06T08:01:15+00:00" }, { "name": "psr/cache", @@ -2986,28 +3023,28 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3030,7 +3067,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -3038,32 +3076,32 @@ "type": "github" } ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3086,7 +3124,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -3094,32 +3133,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3141,7 +3180,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -3149,34 +3189,39 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -3215,41 +3260,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3272,7 +3330,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -3280,33 +3339,33 @@ "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3338,7 +3397,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -3346,27 +3406,27 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -3374,7 +3434,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -3393,7 +3453,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -3401,42 +3461,55 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -3478,46 +3551,56 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -3536,13 +3619,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -3550,33 +3634,33 @@ "type": "github" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3599,7 +3683,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -3607,34 +3692,34 @@ "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3656,7 +3741,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -3664,32 +3750,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3711,7 +3797,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -3719,32 +3806,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3774,94 +3861,53 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3884,37 +3930,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3937,7 +3996,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -3945,7 +4005,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "seld/jsonlint", @@ -4011,48 +4071,125 @@ ], "time": "2024-07-11T14:55:45+00:00" }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "swoole/ide-helper", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "6f12243dce071714c5febe059578d909698f9a52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/6f12243dce071714c5febe059578d909698f9a52", + "reference": "6f12243dce071714c5febe059578d909698f9a52", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole.", + "support": { + "issues": "https://github.com/swoole/ide-helper/issues", + "source": "https://github.com/swoole/ide-helper/tree/6.0.2" + }, + "time": "2025-03-23T07:31:41+00:00" + }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "fcb73f69d655b48fcb894a262f074218df08bd58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/fcb73f69d655b48fcb894a262f074218df08bd58", + "reference": "fcb73f69d655b48fcb894a262f074218df08bd58", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4086,7 +4223,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v8.0.1" }, "funding": [ { @@ -4097,34 +4234,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-12-05T15:25:33+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4152,7 +4293,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -4163,32 +4304,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/7598dd5770580fa3517ec83e8da0c9b9e01f4291", + "reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4216,7 +4361,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v8.0.0" }, "funding": [ { @@ -4227,29 +4372,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2025-11-05T14:36:47+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.2.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -4283,7 +4432,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -4294,16 +4443,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-20T11:17:29+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4362,7 +4515,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -4373,6 +4526,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4382,16 +4539,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -4440,7 +4597,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -4451,16 +4608,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4521,7 +4682,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -4532,6 +4693,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4541,20 +4706,20 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/a0a750500c4ce900d69ba4e9faf16f82c10ee149", + "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -4582,7 +4747,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v8.0.0" }, "funding": [ { @@ -4593,44 +4758,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-10-16T16:25:44+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4669,7 +4837,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v8.0.1" }, "funding": [ { @@ -4680,25 +4848,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -4727,7 +4899,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -4735,7 +4907,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "webmozart/glob", @@ -4789,7 +4961,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4797,6 +4969,6 @@ "ext-opentelemetry": "*", "php": ">=8.0" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/phpunit.xml b/phpunit.xml index de6deb0..af6752f 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,18 @@ + cacheDirectory=".phpunit.cache" +> - - ./tests/e2e/Client.php - ./tests/ + + ./tests + + + ./src + + \ No newline at end of file From be9812781c69f9fd5b3b223656b159683d52c68c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:01:07 +1300 Subject: [PATCH 04/18] Add custom exception --- src/Telemetry/Exception.php | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/Telemetry/Exception.php diff --git a/src/Telemetry/Exception.php b/src/Telemetry/Exception.php new file mode 100644 index 0000000..8dd38a9 --- /dev/null +++ b/src/Telemetry/Exception.php @@ -0,0 +1,7 @@ + Date: Wed, 17 Dec 2025 15:47:02 +1300 Subject: [PATCH 05/18] Add tests --- .../OpenTelemetry/Swoole/MockOtlpServer.php | 205 +++++++++++++++ .../Swoole/TransportIntegrationTest.php | 227 +++++++++++++++++ .../OpenTelemetry/Swoole/TransportTest.php | 206 +++++++++++++++ .../Adapter/OpenTelemetryTestCase.php | 238 ++++++++++++++++++ 4 files changed, 876 insertions(+) create mode 100644 tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php create mode 100644 tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportIntegrationTest.php create mode 100644 tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php create mode 100644 tests/Telemetry/Adapter/OpenTelemetryTestCase.php diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php new file mode 100644 index 0000000..7ae09de --- /dev/null +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php @@ -0,0 +1,205 @@ +, timestamp: float}> */ + private array $requests = []; + + private int $statusCode = 200; + + private string $responseBody = ''; + + private float $responseDelay = 0; + + public function __construct() + { + $this->port = self::HOST === '127.0.0.1' ? 19318 + (self::$portOffset++) : 19318; + } + + /** + * Get the endpoint URL for this server. + */ + public function getEndpoint(): string + { + return 'http://'.self::HOST.':'.$this->port.'/v1/metrics'; + } + + /** + * Get just the port number. + */ + public function getPort(): int + { + return $this->port; + } + + /** + * Configure the response status code. + */ + public function respondWith(int $statusCode, string $body = ''): self + { + $this->statusCode = $statusCode; + $this->responseBody = $body; + + return $this; + } + + /** + * Add artificial delay to responses (for testing timeouts/concurrency). + */ + public function withDelay(float $seconds): self + { + $this->responseDelay = $seconds; + + return $this; + } + + /** + * Start the server (non-blocking). + */ + public function start(): void + { + $this->server = new Server(self::HOST, $this->port); + $this->server->set(['open_http2_protocol' => false]); + + $this->server->handle('/v1/metrics', function (Request $request, Response $response) { + $this->requests[] = [ + 'payload' => $request->getContent(), + 'headers' => $request->header, + 'timestamp' => microtime(true), + ]; + + if ($this->responseDelay > 0) { + \Swoole\Coroutine::sleep($this->responseDelay); + } + + $response->status($this->statusCode); + $response->end($this->responseBody); + }); + + go(fn () => $this->server->start()); + + $this->waitUntilReady(); + } + + /** + * Stop the server. + */ + public function stop(): void + { + $this->server->shutdown(); + } + + /** + * Wait for the server to be ready to accept connections. + */ + private function waitUntilReady(float $timeout = 2.0): void + { + $start = microtime(true); + while (microtime(true) - $start < $timeout) { + $client = new Client(self::HOST, $this->port); + $client->set(['timeout' => 0.1]); + $client->get('/'); + if ($client->statusCode !== -1 && $client->statusCode !== -2) { + $client->close(); + + return; + } + $client->close(); + \Swoole\Coroutine::sleep(0.01); + } + } + + /** + * Get all captured requests. + * + * @return array, timestamp: float}> + */ + public function getRequests(): array + { + return $this->requests; + } + + /** + * Get the number of requests received. + */ + public function getRequestCount(): int + { + return count($this->requests); + } + + /** + * Get the last request received. + * + * @return array{payload: string, headers: array, timestamp: float}|null + */ + public function getLastRequest(): ?array + { + return $this->requests[count($this->requests) - 1] ?? null; + } + + /** + * Clear captured requests. + */ + public function reset(): void + { + $this->requests = []; + } + + /** + * Run a test with a mock server, handling lifecycle automatically. + * + * If already inside a coroutine, runs directly. Otherwise wraps in run(). + * + * @param callable(MockOtlpServer): void $test + */ + public static function run(callable $test): void + { + $exception = null; + + $executor = function () use ($test, &$exception) { + $server = new self(); + $server->start(); + + try { + $test($server); + } catch (\Throwable $e) { + $exception = $e; + } finally { + $server->stop(); + } + }; + + // Check if already in coroutine context + if (\Swoole\Coroutine::getCid() > 0) { + $executor(); + } else { + \Swoole\Coroutine\run($executor); + } + + if ($exception !== null) { + throw $exception; + } + } +} diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportIntegrationTest.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportIntegrationTest.php new file mode 100644 index 0000000..ee04940 --- /dev/null +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportIntegrationTest.php @@ -0,0 +1,227 @@ +respondWith(200, 'OK'); + + $transport = new Transport($server->getEndpoint()); + $testPayload = 'test-metric-payload-data'; + + $result = $transport->send($testPayload)->await(); + + $this->assertEquals('OK', $result); + + $request = $server->getLastRequest(); + $this->assertEquals($testPayload, $request['payload']); + $this->assertEquals(ContentTypes::PROTOBUF, $request['headers']['content-type']); + $this->assertEquals((string) strlen($testPayload), $request['headers']['content-length']); + + $transport->shutdown(); + }); + } + + public function testSendWithCustomHeaders(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport( + endpoint: $server->getEndpoint(), + headers: [ + 'Authorization' => 'Bearer test-token', + 'X-Custom-Header' => 'custom-value', + ] + ); + + $transport->send('payload')->await(); + + $request = $server->getLastRequest(); + $this->assertEquals('Bearer test-token', $request['headers']['authorization']); + $this->assertEquals('custom-value', $request['headers']['x-custom-header']); + + $transport->shutdown(); + }); + } + + public function testSendHandlesServerError(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $server->respondWith(500, 'Internal Server Error'); + + $transport = new Transport($server->getEndpoint()); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('500'); + + try { + $transport->send('payload')->await(); + } finally { + $transport->shutdown(); + } + }); + } + + public function testMultipleSequentialSends(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport( + endpoint: $server->getEndpoint(), + poolSize: 2, + ); + + for ($i = 0; $i < 10; $i++) { + $transport->send("payload-$i")->await(); + } + + $this->assertEquals(10, $server->getRequestCount()); + + $transport->shutdown(); + }); + } + + public function testConcurrentSends(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $server->withDelay(0.01); + + $transport = new Transport( + endpoint: $server->getEndpoint(), + poolSize: 4, + ); + + $wg = new \Swoole\Coroutine\WaitGroup(); + $concurrentRequests = 20; + + for ($i = 0; $i < $concurrentRequests; $i++) { + $wg->add(); + go(function () use ($transport, $i, $wg) { + $transport->send("concurrent-payload-$i")->await(); + $wg->done(); + }); + } + + $wg->wait(); + + $this->assertEquals($concurrentRequests, $server->getRequestCount()); + + $transport->shutdown(); + }); + } + + public function testJsonContentType(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport( + endpoint: $server->getEndpoint(), + contentType: ContentTypes::JSON, + ); + + $transport->send('{"metrics":[]}')->await(); + + $request = $server->getLastRequest(); + $this->assertEquals(ContentTypes::JSON, $request['headers']['content-type']); + + $transport->shutdown(); + }); + } + + public function testConnectionTimeout(): void + { + $exception = null; + + run(function () use (&$exception) { + $transport = new Transport( + endpoint: 'http://127.0.0.1:19999/v1/metrics', + timeout: 0.5, + ); + + $startTime = microtime(true); + + try { + $transport->send('payload')->await(); + } catch (Exception $e) { + $exception = $e; + } finally { + $elapsed = microtime(true) - $startTime; + $this->assertLessThan(2.0, $elapsed); + $transport->shutdown(); + } + }); + + $this->assertNotNull($exception); + $this->assertInstanceOf(Exception::class, $exception); + } + + public function testKeepAliveConnectionReuse(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport( + endpoint: $server->getEndpoint(), + poolSize: 1, + ); + + // Send 5 requests with pool size 1 + for ($i = 0; $i < 5; $i++) { + $transport->send("payload-$i")->await(); + } + + $this->assertEquals(5, $server->getRequestCount()); + + $transport->shutdown(); + }); + } + + public function testLargePayload(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport($server->getEndpoint()); + + // 1MB payload + $largePayload = str_repeat('x', 1024 * 1024); + + $transport->send($largePayload)->await(); + + $request = $server->getLastRequest(); + $this->assertEquals(strlen($largePayload), strlen($request['payload'])); + + $transport->shutdown(); + }); + } + + public function testServerResetsRequestTracking(): void + { + MockOtlpServer::run(function (MockOtlpServer $server) { + $transport = new Transport($server->getEndpoint()); + + $transport->send('first')->await(); + $this->assertEquals(1, $server->getRequestCount()); + + $server->reset(); + $this->assertEquals(0, $server->getRequestCount()); + + $transport->send('second')->await(); + $this->assertEquals(1, $server->getRequestCount()); + + $transport->shutdown(); + }); + } +} diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php new file mode 100644 index 0000000..4934b29 --- /dev/null +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php @@ -0,0 +1,206 @@ +assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testConstructorWithHttpEndpoint(): void + { + $transport = new Transport('http://localhost:4318/v1/metrics'); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testConstructorWithCustomContentType(): void + { + $transport = new Transport( + 'http://localhost:4318/v1/metrics', + ContentTypes::JSON + ); + + $this->assertEquals(ContentTypes::JSON, $transport->contentType()); + } + + public function testConstructorWithCustomHeaders(): void + { + $transport = new Transport( + 'http://localhost:4318/v1/metrics', + ContentTypes::PROTOBUF, + ['Authorization' => 'Bearer token123'] + ); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testConstructorWithCustomTimeout(): void + { + $transport = new Transport( + 'http://localhost:4318/v1/metrics', + ContentTypes::PROTOBUF, + [], + 5.0 + ); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testConstructorWithCustomPoolSize(): void + { + $transport = new Transport( + 'http://localhost:4318/v1/metrics', + ContentTypes::PROTOBUF, + [], + 10.0, + 16 + ); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testConstructorWithCustomSocketBufferSize(): void + { + $transport = new Transport( + 'http://localhost:4318/v1/metrics', + ContentTypes::PROTOBUF, + [], + 10.0, + 8, + 128 * 1024 + ); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testShutdownReturnsTrue(): void + { + $this->runInCoroutine(function () { + $transport = new Transport('http://localhost:4318/v1/metrics'); + + $result = $transport->shutdown(); + + $this->assertTrue($result); + }); + } + + public function testForceFlushReturnsTrue(): void + { + $this->runInCoroutine(function () { + $transport = new Transport('http://localhost:4318/v1/metrics'); + + $result = $transport->forceFlush(); + + $this->assertTrue($result); + }); + } + + public function testSendAfterShutdownReturnsError(): void + { + $this->runInCoroutine(function () { + $transport = new Transport('http://localhost:4318/v1/metrics'); + + $transport->shutdown(); + + $future = $transport->send('test payload'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Transport has been shut down'); + $future->await(); + }); + } + + public function testMultipleShutdownsAreSafe(): void + { + $this->runInCoroutine(function () { + $transport = new Transport('http://localhost:4318/v1/metrics'); + + $result1 = $transport->shutdown(); + $result2 = $transport->shutdown(); + + $this->assertTrue($result1); + $this->assertTrue($result2); + }); + } + + public function testEndpointWithQueryString(): void + { + $transport = new Transport('http://localhost:4318/v1/metrics?api_key=secret&env=test'); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testEndpointDefaultsToLocalhost(): void + { + // Malformed URL should still create transport (defaults to localhost) + $transport = new Transport('http:///v1/metrics'); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testDefaultPortForHttp(): void + { + $transport = new Transport('http://example.com/v1/metrics'); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testDefaultPortForHttps(): void + { + $transport = new Transport('https://example.com/v1/metrics'); + + $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + } + + public function testContentTypeMatchesConstructor(): void + { + $jsonTransport = new Transport('http://localhost:4318', ContentTypes::JSON); + $protobufTransport = new Transport('http://localhost:4318', ContentTypes::PROTOBUF); + + $this->assertEquals(ContentTypes::JSON, $jsonTransport->contentType()); + $this->assertEquals(ContentTypes::PROTOBUF, $protobufTransport->contentType()); + } +} diff --git a/tests/Telemetry/Adapter/OpenTelemetryTestCase.php b/tests/Telemetry/Adapter/OpenTelemetryTestCase.php new file mode 100644 index 0000000..bab4da3 --- /dev/null +++ b/tests/Telemetry/Adapter/OpenTelemetryTestCase.php @@ -0,0 +1,238 @@ + 'test-namespace', + 'name' => 'test-service', + 'instanceId' => 'test-instance-'.uniqid(), + ]; + } + + protected function setUp(): void + { + parent::setUp(); + + $this->transport = $this->createTransport(); + $config = $this->getServiceConfig(); + + $this->adapter = new OpenTelemetry( + endpoint: $this->getEndpoint(), + serviceNamespace: $config['namespace'], + serviceName: $config['name'], + serviceInstanceId: $config['instanceId'], + transport: $this->transport, + ); + } + + public function testCreateCounter(): void + { + $counter = $this->adapter->createCounter( + name: 'test_counter', + unit: 'requests', + description: 'Test counter metric' + ); + + $this->assertInstanceOf(Counter::class, $counter); + } + + public function testCounterAdd(): void + { + $counter = $this->adapter->createCounter('add_test_counter'); + + // Should not throw + $counter->add(1); + $counter->add(5); + $counter->add(10, ['label' => 'value']); + + $this->assertTrue(true); + } + + public function testCreateHistogram(): void + { + $histogram = $this->adapter->createHistogram( + name: 'test_histogram', + unit: 'ms', + description: 'Test histogram metric' + ); + + $this->assertInstanceOf(Histogram::class, $histogram); + } + + public function testHistogramRecord(): void + { + $histogram = $this->adapter->createHistogram('record_test_histogram'); + + // Should not throw + $histogram->record(100); + $histogram->record(250.5); + $histogram->record(50, ['endpoint' => '/api/test']); + + $this->assertTrue(true); + } + + public function testCreateGauge(): void + { + $gauge = $this->adapter->createGauge( + name: 'test_gauge', + unit: 'bytes', + description: 'Test gauge metric' + ); + + $this->assertInstanceOf(Gauge::class, $gauge); + } + + public function testGaugeRecord(): void + { + $gauge = $this->adapter->createGauge('record_test_gauge'); + + // Should not throw + $gauge->record(1024); + $gauge->record(2048.5); + $gauge->record(512, ['host' => 'server-1']); + + $this->assertTrue(true); + } + + public function testCreateUpDownCounter(): void + { + $upDownCounter = $this->adapter->createUpDownCounter( + name: 'test_updown_counter', + unit: 'connections', + description: 'Test up-down counter metric' + ); + + $this->assertInstanceOf(UpDownCounter::class, $upDownCounter); + } + + public function testUpDownCounterAdd(): void + { + $upDownCounter = $this->adapter->createUpDownCounter('add_test_updown'); + + // Should not throw + $upDownCounter->add(1); + $upDownCounter->add(-1); + $upDownCounter->add(5, ['pool' => 'main']); + + $this->assertTrue(true); + } + + public function testMeterCaching(): void + { + $counter1 = $this->adapter->createCounter('cached_counter'); + $counter2 = $this->adapter->createCounter('cached_counter'); + + // Same name should return cached instance + $this->assertSame($counter1, $counter2); + } + + public function testDifferentMetersNotCached(): void + { + $counter1 = $this->adapter->createCounter('counter_a'); + $counter2 = $this->adapter->createCounter('counter_b'); + + // Different names should return different instances + $this->assertNotSame($counter1, $counter2); + } + + public function testCollect(): void + { + $counter = $this->adapter->createCounter('collect_test_counter'); + $counter->add(1); + + $result = $this->adapter->collect(); + + $this->assertIsBool($result); + } + + public function testMetricsWithAttributes(): void + { + $counter = $this->adapter->createCounter('attributed_counter'); + $histogram = $this->adapter->createHistogram('attributed_histogram'); + $gauge = $this->adapter->createGauge('attributed_gauge'); + $upDownCounter = $this->adapter->createUpDownCounter('attributed_updown'); + + $attributes = [ + 'service' => 'api', + 'environment' => 'test', + 'version' => '1.0.0', + ]; + + // All should accept attributes without throwing + $counter->add(1, $attributes); + $histogram->record(100, $attributes); + $gauge->record(50, $attributes); + $upDownCounter->add(1, $attributes); + + $this->assertTrue(true); + } + + public function testMetricsWithAdvisory(): void + { + // Create metrics with advisory parameters + $counter = $this->adapter->createCounter( + name: 'advisory_counter', + unit: 'items', + description: 'Counter with advisory', + advisory: ['explicit_bucket_boundaries' => [0, 5, 10, 25, 50, 100]] + ); + + $this->assertInstanceOf(Counter::class, $counter); + } + + public function testNullOptionalParameters(): void + { + // All optional parameters as null + $counter = $this->adapter->createCounter('minimal_counter'); + $histogram = $this->adapter->createHistogram('minimal_histogram'); + $gauge = $this->adapter->createGauge('minimal_gauge'); + $upDownCounter = $this->adapter->createUpDownCounter('minimal_updown'); + + $this->assertInstanceOf(Counter::class, $counter); + $this->assertInstanceOf(Histogram::class, $histogram); + $this->assertInstanceOf(Gauge::class, $gauge); + $this->assertInstanceOf(UpDownCounter::class, $upDownCounter); + } +} From 7074393b41d8dee9645cf6fec11fb7a2147f29e0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:48:17 +1300 Subject: [PATCH 06/18] Add test infra --- .github/workflows/tests.yml | 46 +++++++++++++++++++++++++++++++ docker-compose.yml | 36 ++++++++++++++++++++++++ docker/Dockerfile | 44 +++++++++++++++++++++++++++++ docker/otel-collector-config.yaml | 31 +++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/otel-collector-config.yaml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..faa2ebd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,46 @@ +name: "Tests" + +on: + pull_request: + push: + branches: + - main + +jobs: + tests: + name: PHP ${{ matrix.php }} Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.2', '8.3', '8.4'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: swoole, sockets, protobuf, opentelemetry + tools: composer:v2 + coverage: none + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run tests + run: composer test \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be4247f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + # OTLP Collector for integration testing + otel-collector: + image: otel/opentelemetry-collector:latest + container_name: telemetry-otel-collector + command: ["--config=/etc/otel/config.yaml"] + volumes: + - ./docker/otel-collector-config.yaml:/etc/otel/config.yaml:ro + ports: + - "4317:4317" # gRPC + - "4318:4318" # HTTP + networks: + - telemetry + + # PHP with Swoole for running tests + # Usage: docker compose run --rm php composer test + php: + build: + context: . + dockerfile: docker/Dockerfile + volumes: + - .:/app + - composer-cache:/root/.composer/cache + working_dir: /app + depends_on: + - otel-collector + environment: + - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 + networks: + - telemetry + +networks: + telemetry: + +volumes: + composer-cache: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6587028 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,44 @@ +FROM php:8.4-cli + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + unzip \ + libssl-dev \ + libcurl4-openssl-dev \ + libprotobuf-dev \ + protobuf-compiler \ + && rm -rf /var/lib/apt/lists/* + +# Install Swoole +RUN pecl install swoole && docker-php-ext-enable swoole + +# Install protobuf extension +RUN pecl install protobuf && docker-php-ext-enable protobuf + +# Install OpenTelemetry extension +RUN pecl install opentelemetry && docker-php-ext-enable opentelemetry + +# Install sockets extension +RUN docker-php-ext-install sockets + +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Set working directory +WORKDIR /app + +# Copy composer files first for better caching +COPY composer.json composer.lock ./ + +# Install dependencies +RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist + +# Copy the rest of the application +COPY . . + +# Generate autoloader +RUN composer dump-autoload --optimize + +# Default command +CMD ["php", "-v"] diff --git a/docker/otel-collector-config.yaml b/docker/otel-collector-config.yaml new file mode 100644 index 0000000..3c08894 --- /dev/null +++ b/docker/otel-collector-config.yaml @@ -0,0 +1,31 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + timeout: 1s + send_batch_size: 1024 + +exporters: + debug: + verbosity: detailed + +service: + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [debug] + traces: + receivers: [otlp] + processors: [batch] + exporters: [debug] + logs: + receivers: [otlp] + processors: [batch] + exporters: [debug] From a2bc9459ab12ca0b9b147c6c4eb967ea268baa1b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:48:53 +1300 Subject: [PATCH 07/18] Fix pop --- src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php b/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php index 0274127..59a4af5 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php @@ -94,7 +94,7 @@ public function contentType(): string */ private function popClient(): Client { - $client = $this->pool->pop(0); + $client = $this->pool->pop(0.001); if ($client === false) { $client = $this->pool->pop(0.05); } From 0dd326cb7f392bcd34e56a60e9847414345bd4a3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:52:36 +1300 Subject: [PATCH 08/18] Restructure --- .../Transport.php => Transport/Swoole.php} | 4 +- .../Swoole/TransportIntegrationTest.php | 22 +++++------ .../OpenTelemetry/Swoole/TransportTest.php | 38 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) rename src/Telemetry/Adapter/OpenTelemetry/{Swoole/Transport.php => Transport/Swoole.php} (98%) diff --git a/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php similarity index 98% rename from src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php rename to src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php index 59a4af5..38699ee 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Swoole/Transport.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php @@ -1,6 +1,6 @@ respondWith(200, 'OK'); - $transport = new Transport($server->getEndpoint()); + $transport = new Swoole($server->getEndpoint()); $testPayload = 'test-metric-payload-data'; $result = $transport->send($testPayload)->await(); @@ -44,7 +44,7 @@ public function testSendPayloadToServer(): void public function testSendWithCustomHeaders(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport( + $transport = new Swoole( endpoint: $server->getEndpoint(), headers: [ 'Authorization' => 'Bearer test-token', @@ -67,7 +67,7 @@ public function testSendHandlesServerError(): void MockOtlpServer::run(function (MockOtlpServer $server) { $server->respondWith(500, 'Internal Server Error'); - $transport = new Transport($server->getEndpoint()); + $transport = new Swoole($server->getEndpoint()); $this->expectException(Exception::class); $this->expectExceptionMessage('500'); @@ -83,7 +83,7 @@ public function testSendHandlesServerError(): void public function testMultipleSequentialSends(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport( + $transport = new Swoole( endpoint: $server->getEndpoint(), poolSize: 2, ); @@ -103,7 +103,7 @@ public function testConcurrentSends(): void MockOtlpServer::run(function (MockOtlpServer $server) { $server->withDelay(0.01); - $transport = new Transport( + $transport = new Swoole( endpoint: $server->getEndpoint(), poolSize: 4, ); @@ -130,7 +130,7 @@ public function testConcurrentSends(): void public function testJsonContentType(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport( + $transport = new Swoole( endpoint: $server->getEndpoint(), contentType: ContentTypes::JSON, ); @@ -149,7 +149,7 @@ public function testConnectionTimeout(): void $exception = null; run(function () use (&$exception) { - $transport = new Transport( + $transport = new Swoole( endpoint: 'http://127.0.0.1:19999/v1/metrics', timeout: 0.5, ); @@ -174,7 +174,7 @@ public function testConnectionTimeout(): void public function testKeepAliveConnectionReuse(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport( + $transport = new Swoole( endpoint: $server->getEndpoint(), poolSize: 1, ); @@ -193,7 +193,7 @@ public function testKeepAliveConnectionReuse(): void public function testLargePayload(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport($server->getEndpoint()); + $transport = new Swoole($server->getEndpoint()); // 1MB payload $largePayload = str_repeat('x', 1024 * 1024); @@ -210,7 +210,7 @@ public function testLargePayload(): void public function testServerResetsRequestTracking(): void { MockOtlpServer::run(function (MockOtlpServer $server) { - $transport = new Transport($server->getEndpoint()); + $transport = new Swoole($server->getEndpoint()); $transport->send('first')->await(); $this->assertEquals(1, $server->getRequestCount()); diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php index 4934b29..bd6dc5c 100644 --- a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php @@ -5,7 +5,7 @@ use OpenTelemetry\Contrib\Otlp\ContentTypes; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; -use Utopia\Telemetry\Adapter\OpenTelemetry\Swoole\Transport; +use Utopia\Telemetry\Adapter\OpenTelemetry\Transport\Swoole; use Utopia\Telemetry\Exception; use function Swoole\Coroutine\run; @@ -44,21 +44,21 @@ private function runInCoroutine(callable $callback): mixed public function testConstructorParsesEndpointCorrectly(): void { - $transport = new Transport('https://otel.example.com:4318/v1/metrics?foo=bar'); + $transport = new Swoole('https://otel.example.com:4318/v1/metrics?foo=bar'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } public function testConstructorWithHttpEndpoint(): void { - $transport = new Transport('http://localhost:4318/v1/metrics'); + $transport = new Swoole('http://localhost:4318/v1/metrics'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } public function testConstructorWithCustomContentType(): void { - $transport = new Transport( + $transport = new Swoole( 'http://localhost:4318/v1/metrics', ContentTypes::JSON ); @@ -68,7 +68,7 @@ public function testConstructorWithCustomContentType(): void public function testConstructorWithCustomHeaders(): void { - $transport = new Transport( + $transport = new Swoole( 'http://localhost:4318/v1/metrics', ContentTypes::PROTOBUF, ['Authorization' => 'Bearer token123'] @@ -79,7 +79,7 @@ public function testConstructorWithCustomHeaders(): void public function testConstructorWithCustomTimeout(): void { - $transport = new Transport( + $transport = new Swoole( 'http://localhost:4318/v1/metrics', ContentTypes::PROTOBUF, [], @@ -91,7 +91,7 @@ public function testConstructorWithCustomTimeout(): void public function testConstructorWithCustomPoolSize(): void { - $transport = new Transport( + $transport = new Swoole( 'http://localhost:4318/v1/metrics', ContentTypes::PROTOBUF, [], @@ -104,7 +104,7 @@ public function testConstructorWithCustomPoolSize(): void public function testConstructorWithCustomSocketBufferSize(): void { - $transport = new Transport( + $transport = new Swoole( 'http://localhost:4318/v1/metrics', ContentTypes::PROTOBUF, [], @@ -119,7 +119,7 @@ public function testConstructorWithCustomSocketBufferSize(): void public function testShutdownReturnsTrue(): void { $this->runInCoroutine(function () { - $transport = new Transport('http://localhost:4318/v1/metrics'); + $transport = new Swoole('http://localhost:4318/v1/metrics'); $result = $transport->shutdown(); @@ -130,7 +130,7 @@ public function testShutdownReturnsTrue(): void public function testForceFlushReturnsTrue(): void { $this->runInCoroutine(function () { - $transport = new Transport('http://localhost:4318/v1/metrics'); + $transport = new Swoole('http://localhost:4318/v1/metrics'); $result = $transport->forceFlush(); @@ -141,14 +141,14 @@ public function testForceFlushReturnsTrue(): void public function testSendAfterShutdownReturnsError(): void { $this->runInCoroutine(function () { - $transport = new Transport('http://localhost:4318/v1/metrics'); + $transport = new Swoole('http://localhost:4318/v1/metrics'); $transport->shutdown(); $future = $transport->send('test payload'); $this->expectException(Exception::class); - $this->expectExceptionMessage('Transport has been shut down'); + $this->expectExceptionMessage('Swoole has been shut down'); $future->await(); }); } @@ -156,7 +156,7 @@ public function testSendAfterShutdownReturnsError(): void public function testMultipleShutdownsAreSafe(): void { $this->runInCoroutine(function () { - $transport = new Transport('http://localhost:4318/v1/metrics'); + $transport = new Swoole('http://localhost:4318/v1/metrics'); $result1 = $transport->shutdown(); $result2 = $transport->shutdown(); @@ -168,7 +168,7 @@ public function testMultipleShutdownsAreSafe(): void public function testEndpointWithQueryString(): void { - $transport = new Transport('http://localhost:4318/v1/metrics?api_key=secret&env=test'); + $transport = new Swoole('http://localhost:4318/v1/metrics?api_key=secret&env=test'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } @@ -176,29 +176,29 @@ public function testEndpointWithQueryString(): void public function testEndpointDefaultsToLocalhost(): void { // Malformed URL should still create transport (defaults to localhost) - $transport = new Transport('http:///v1/metrics'); + $transport = new Swoole('http:///v1/metrics'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } public function testDefaultPortForHttp(): void { - $transport = new Transport('http://example.com/v1/metrics'); + $transport = new Swoole('http://example.com/v1/metrics'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } public function testDefaultPortForHttps(): void { - $transport = new Transport('https://example.com/v1/metrics'); + $transport = new Swoole('https://example.com/v1/metrics'); $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } public function testContentTypeMatchesConstructor(): void { - $jsonTransport = new Transport('http://localhost:4318', ContentTypes::JSON); - $protobufTransport = new Transport('http://localhost:4318', ContentTypes::PROTOBUF); + $jsonTransport = new Swoole('http://localhost:4318', ContentTypes::JSON); + $protobufTransport = new Swoole('http://localhost:4318', ContentTypes::PROTOBUF); $this->assertEquals(ContentTypes::JSON, $jsonTransport->contentType()); $this->assertEquals(ContentTypes::PROTOBUF, $protobufTransport->contentType()); From 1fda8bff4d9ab31e219c14a7acbd03ea857b0020 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:55:19 +1300 Subject: [PATCH 09/18] Composer update --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index faa2ebd..d390fba 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,7 +40,7 @@ jobs: ${{ runner.os }}-php-${{ matrix.php }}-composer- - name: Install dependencies - run: composer install --prefer-dist --no-progress + run: composer update --prefer-dist --no-progress - name: Run tests run: composer test \ No newline at end of file From 20c35bd401d0a4e601b6faee9fc00f26f73235db Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 15:56:54 +1300 Subject: [PATCH 10/18] Fix expectation --- tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php index bd6dc5c..33f71b3 100644 --- a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php @@ -148,7 +148,7 @@ public function testSendAfterShutdownReturnsError(): void $future = $transport->send('test payload'); $this->expectException(Exception::class); - $this->expectExceptionMessage('Swoole has been shut down'); + $this->expectExceptionMessage('Transport has been shut down'); $future->await(); }); } From 99669229ab36e3e289e16ae527b6abfd89b8eae8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 16:21:44 +1300 Subject: [PATCH 11/18] PHPStan max --- phpstan.neon | 2 +- src/Telemetry/Adapter.php | 12 ++++ src/Telemetry/Adapter/None.php | 24 ++++++++ src/Telemetry/Adapter/OpenTelemetry.php | 39 ++++++++++++- .../OpenTelemetry/Transport/Swoole.php | 31 ++++++++--- src/Telemetry/Adapter/Test.php | 55 +++++++++++++++++++ src/Telemetry/Counter.php | 3 + src/Telemetry/Gauge.php | 3 + src/Telemetry/Histogram.php | 3 + src/Telemetry/UpDownCounter.php | 3 + 10 files changed, 163 insertions(+), 12 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8886f7f..00c5561 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,4 @@ parameters: - level: 5 + level: max paths: - src \ No newline at end of file diff --git a/src/Telemetry/Adapter.php b/src/Telemetry/Adapter.php index 7c3048f..616c45e 100644 --- a/src/Telemetry/Adapter.php +++ b/src/Telemetry/Adapter.php @@ -4,6 +4,9 @@ interface Adapter { + /** + * @param array $advisory + */ public function createCounter( string $name, ?string $unit = null, @@ -11,6 +14,9 @@ public function createCounter( array $advisory = [], ): Counter; + /** + * @param array $advisory + */ public function createHistogram( string $name, ?string $unit = null, @@ -18,6 +24,9 @@ public function createHistogram( array $advisory = [], ): Histogram; + /** + * @param array $advisory + */ public function createGauge( string $name, ?string $unit = null, @@ -25,6 +34,9 @@ public function createGauge( array $advisory = [], ): Gauge; + /** + * @param array $advisory + */ public function createUpDownCounter( string $name, ?string $unit = null, diff --git a/src/Telemetry/Adapter/None.php b/src/Telemetry/Adapter/None.php index 495a8f5..cb1285b 100644 --- a/src/Telemetry/Adapter/None.php +++ b/src/Telemetry/Adapter/None.php @@ -10,36 +10,60 @@ class None implements Adapter { + /** + * @param array $advisory + */ public function createCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Counter { return new class () extends Counter { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { } }; } + /** + * @param array $advisory + */ public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Histogram { return new class () extends Histogram { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { } }; } + /** + * @param array $advisory + */ public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Gauge { return new class () extends Gauge { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { } }; } + /** + * @param array $advisory + */ public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounter { return new class () extends UpDownCounter { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { } diff --git a/src/Telemetry/Adapter/OpenTelemetry.php b/src/Telemetry/Adapter/OpenTelemetry.php index a674a91..b2ff293 100644 --- a/src/Telemetry/Adapter/OpenTelemetry.php +++ b/src/Telemetry/Adapter/OpenTelemetry.php @@ -33,6 +33,9 @@ class OpenTelemetry implements Adapter private MeterInterface $meter; + /** + * @var array> + */ private array $meterStorage = [ Counter::class => [], UpDownCounter::class => [], @@ -45,7 +48,7 @@ class OpenTelemetry implements Adapter * @param string $serviceNamespace * @param string $serviceName * @param string $serviceInstanceId - * @param TransportInterface|null $transport + * @param TransportInterface|null $transport */ public function __construct( string $endpoint, @@ -72,6 +75,8 @@ public function __construct( /** * Initialize Meter + * + * @param AttributesInterface $attributes */ protected function initMeter(MetricExporterInterface $exporter, AttributesInterface $attributes): MeterInterface { @@ -88,23 +93,35 @@ protected function initMeter(MetricExporterInterface $exporter, AttributesInterf /** * Create Metric Exporter + * + * @param TransportInterface $transport */ protected function createExporter(TransportInterface $transport): MetricExporterInterface { + /** @phpstan-ignore argument.type */ return new MetricExporter($transport, Temporality::CUMULATIVE); } - private function createMeter(string $type, string $name, callable $creator): mixed + /** + * @template T of Counter|UpDownCounter|Histogram|Gauge + * @param class-string $type + * @param callable(): T $creator + * @return T + */ + private function createMeter(string $type, string $name, callable $creator): Counter|UpDownCounter|Histogram|Gauge { if (! isset($this->meterStorage[$type][$name])) { $this->meterStorage[$type][$name] = $creator(); } + /** @var T */ return $this->meterStorage[$type][$name]; } /** * Create a Counter metric + * + * @param array $advisory */ public function createCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Counter { @@ -116,6 +133,9 @@ public function __construct(private CounterInterface $counter) { } + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { $this->counter->add($amount, $attributes); @@ -126,6 +146,8 @@ public function add(float|int $amount, iterable $attributes = []): void /** * Create a Histogram metric + * + * @param array $advisory */ public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Histogram { @@ -137,6 +159,9 @@ public function __construct(private HistogramInterface $histogram) { } + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { $this->histogram->record($amount, $attributes); @@ -147,6 +172,8 @@ public function record(float|int $amount, iterable $attributes = []): void /** * Create a Gauge metric + * + * @param array $advisory */ public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Gauge { @@ -158,6 +185,9 @@ public function __construct(private GaugeInterface $gauge) { } + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { $this->gauge->record($amount, $attributes); @@ -168,6 +198,8 @@ public function record(float|int $amount, iterable $attributes = []): void /** * Create an UpDownCounter metric + * + * @param array $advisory */ public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounter { @@ -179,6 +211,9 @@ public function __construct(private UpDownCounterInterface $upDownCounter) { } + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { $this->upDownCounter->add($amount, $attributes); diff --git a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php index 38699ee..ca24818 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php @@ -18,6 +18,8 @@ * * Uses connection pooling with keep-alive for maximum throughput. * Designed for Swoole's coroutine scheduler without cURL multi-handle conflicts. + * + * @implements TransportInterface */ class Swoole implements TransportInterface { @@ -37,6 +39,10 @@ class Swoole implements TransportInterface private Atomic $shutdown; + /** + * @param 'application/json'|'application/x-ndjson'|'application/x-protobuf' $contentType + * @param array $headers + */ public function __construct( string $endpoint, private string $contentType = ContentTypes::PROTOBUF, @@ -58,11 +64,13 @@ public function __construct( $this->shutdown = new Atomic(0); $this->pool = new Channel($this->poolSize); - $this->baseHeaders = [ - 'Content-Type' => $this->contentType, - 'Connection' => 'keep-alive', - ...$this->headers, - ]; + $this->baseHeaders = \array_merge( + [ + 'Content-Type' => $this->contentType, + 'Connection' => 'keep-alive', + ], + $this->headers + ); $this->settings = [ 'timeout' => $this->timeout, 'connect_timeout' => \min(1.0, $this->timeout), @@ -125,6 +133,9 @@ private function putClient(Client $client, bool $forceClose = false): void } } + /** + * @return FutureInterface + */ public function send(string $payload, ?CancellationInterface $cancellation = null): FutureInterface { if ($this->shutdown->get() === 1) { @@ -145,16 +156,17 @@ public function send(string $payload, ?CancellationInterface $cancellation = nul // Connection error (timeout, reset, etc.) if ($statusCode < 0) { $forceClose = true; - $errCode = $client->errCode; + $errCode = \is_int($client->errCode) ? $client->errCode : 0; $errMsg = \socket_strerror($errCode); - return new ErrorFuture(new Exception("OTLP connection failed: $errMsg (code: $errCode)")); + return new ErrorFuture(new Exception("OTLP connection failed: {$errMsg} (code: {$errCode})")); } $body = $client->getBody(); + $bodyStr = \is_string($body) ? $body : ''; if ($statusCode >= 200 && $statusCode < 300) { - return new CompletedFuture($body); + return new CompletedFuture($bodyStr); } // Server error may need fresh connection @@ -162,7 +174,8 @@ public function send(string $payload, ?CancellationInterface $cancellation = nul $forceClose = true; } - return new ErrorFuture(new Exception("OTLP export failed with status $statusCode: $body")); + $statusCodeStr = \is_int($statusCode) ? (string)$statusCode : 'unknown'; + return new ErrorFuture(new Exception("OTLP export failed with status {$statusCodeStr}: {$bodyStr}")); } catch (\Throwable $e) { $forceClose = true; diff --git a/src/Telemetry/Adapter/Test.php b/src/Telemetry/Adapter/Test.php index 7688597..74df1e0 100644 --- a/src/Telemetry/Adapter/Test.php +++ b/src/Telemetry/Adapter/Test.php @@ -13,15 +13,40 @@ */ class Test implements Adapter { + /** + * @var array + */ public array $counters = []; + + /** + * @var array + */ public array $histograms = []; + + /** + * @var array + */ public array $gauges = []; + + /** + * @var array + */ public array $upDownCounters = []; + /** + * @param array $advisory + */ public function createCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Counter { $counter = new class () extends Counter { + /** + * @var array + */ public array $values = []; + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { $this->values[] = $amount; @@ -31,10 +56,20 @@ public function add(float|int $amount, iterable $attributes = []): void return $counter; } + /** + * @param array $advisory + */ public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Histogram { $histogram = new class () extends Histogram { + /** + * @var array + */ public array $values = []; + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { $this->values[] = $amount; @@ -44,10 +79,20 @@ public function record(float|int $amount, iterable $attributes = []): void return $histogram; } + /** + * @param array $advisory + */ public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Gauge { $gauge = new class () extends Gauge { + /** + * @var array + */ public array $values = []; + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function record(float|int $amount, iterable $attributes = []): void { $this->values[] = $amount; @@ -57,10 +102,20 @@ public function record(float|int $amount, iterable $attributes = []): void return $gauge; } + /** + * @param array $advisory + */ public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounter { $upDownCounter = new class () extends UpDownCounter { + /** + * @var array + */ public array $values = []; + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ public function add(float|int $amount, iterable $attributes = []): void { $this->values[] = $amount; diff --git a/src/Telemetry/Counter.php b/src/Telemetry/Counter.php index 6d00365..c08a5ad 100644 --- a/src/Telemetry/Counter.php +++ b/src/Telemetry/Counter.php @@ -4,5 +4,8 @@ abstract class Counter { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ abstract public function add(float|int $amount, iterable $attributes = []): void; } diff --git a/src/Telemetry/Gauge.php b/src/Telemetry/Gauge.php index 8aa6581..245830f 100644 --- a/src/Telemetry/Gauge.php +++ b/src/Telemetry/Gauge.php @@ -4,5 +4,8 @@ abstract class Gauge { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ abstract public function record(float|int $amount, iterable $attributes = []): void; } diff --git a/src/Telemetry/Histogram.php b/src/Telemetry/Histogram.php index 27cc919..f423bec 100644 --- a/src/Telemetry/Histogram.php +++ b/src/Telemetry/Histogram.php @@ -4,5 +4,8 @@ abstract class Histogram { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ abstract public function record(float|int $amount, iterable $attributes = []): void; } diff --git a/src/Telemetry/UpDownCounter.php b/src/Telemetry/UpDownCounter.php index 98a9648..4d1fbce 100644 --- a/src/Telemetry/UpDownCounter.php +++ b/src/Telemetry/UpDownCounter.php @@ -4,5 +4,8 @@ abstract class UpDownCounter { + /** + * @param iterable|bool|float|int|string|null> $attributes + */ abstract public function add(float|int $amount, iterable $attributes = []): void; } From e3561373cab06467d266c10ad137dfc95fee4591 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 16:55:21 +1300 Subject: [PATCH 12/18] Update docker/Dockerfile Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6587028..fe1e319 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,7 +32,7 @@ WORKDIR /app COPY composer.json composer.lock ./ # Install dependencies -RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist +RUN composer install --no-scripts --no-autoloader --prefer-dist # Copy the rest of the application COPY . . From ec12d1d528d4f07b3231111bf01bf0843331a606 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 18:42:35 +1300 Subject: [PATCH 13/18] Fix test stack --- .github/workflows/tests.yml | 27 +--- composer.lock | 127 +++--------------- docker-compose.yml | 6 +- docker/Dockerfile | 41 ++---- .../OpenTelemetry/Transport/Swoole.php | 82 +++++------ .../OpenTelemetry/Swoole/MockOtlpServer.php | 21 ++- .../OpenTelemetry/Swoole/TransportTest.php | 18 ++- 7 files changed, 106 insertions(+), 216 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d390fba..649505d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: jobs: tests: - name: PHP ${{ matrix.php }} Tests + name: Tests (PHP ${{ matrix.php }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -19,28 +19,5 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: swoole, sockets, protobuf, opentelemetry - tools: composer:v2 - coverage: none - - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php-${{ matrix.php }}-composer- - - - name: Install dependencies - run: composer update --prefer-dist --no-progress - - name: Run tests - run: composer test \ No newline at end of file + run: PHP_VERSION=${{ matrix.php }} docker compose run --rm tests composer test \ No newline at end of file diff --git a/composer.lock b/composer.lock index 17926e0..d0acabd 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": "0bc07196160002f85ff89cf37156e082", + "content-hash": "1682111617e78148dc4167a1359ac72d", "packages": [ { "name": "brick/math", @@ -1306,31 +1306,27 @@ }, { "name": "symfony/http-client", - "version": "v7.4.1", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007" + "reference": "727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007", + "url": "https://api.github.com/repos/symfony/http-client/zipball/727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0", + "reference": "727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "amphp/amp": "<2.5", - "amphp/socket": "<1.1", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" + "amphp/amp": "<3", + "php-http/discovery": "<1.15" }, "provide": { "php-http/async-client-implementation": "*", @@ -1339,20 +1335,19 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", + "amphp/http-client": "^5.3.2", + "amphp/http-tunnel": "^2.0", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1383,7 +1378,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.1" + "source": "https://github.com/symfony/http-client/tree/v8.0.1" }, "funding": [ { @@ -1403,7 +1398,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T21:12:57+00:00" + "time": "2025-12-05T14:08:45+00:00" }, { "name": "symfony/http-client-contracts", @@ -1648,86 +1643,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php83", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-08T02:45:35+00:00" - }, { "name": "symfony/service-contracts", "version": "v3.6.1", @@ -4961,7 +4876,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4969,6 +4884,6 @@ "ext-opentelemetry": "*", "php": ">=8.0" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.9.0" } diff --git a/docker-compose.yml b/docker-compose.yml index be4247f..fedcb8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,12 @@ services: networks: - telemetry - # PHP with Swoole for running tests - # Usage: docker compose run --rm php composer test - php: + tests: build: context: . dockerfile: docker/Dockerfile + args: + PHP_VERSION: ${PHP_VERSION:-8.4} volumes: - .:/app - composer-cache:/root/.composer/cache diff --git a/docker/Dockerfile b/docker/Dockerfile index fe1e319..43fe46a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,44 +1,25 @@ -FROM php:8.4-cli +ARG PHP_VERSION=8.4 +FROM php:${PHP_VERSION}-cli-alpine -# Install system dependencies -RUN apt-get update && apt-get install -y \ +RUN apk add --no-cache \ git \ unzip \ - libssl-dev \ - libcurl4-openssl-dev \ - libprotobuf-dev \ - protobuf-compiler \ - && rm -rf /var/lib/apt/lists/* + openssl-dev \ + curl-dev \ + protobuf-dev \ + linux-headers \ + $PHPIZE_DEPS -# Install Swoole -RUN pecl install swoole && docker-php-ext-enable swoole +RUN pecl install opentelemetry protobuf swoole && \ + docker-php-ext-enable opentelemetry protobuf swoole -# Install protobuf extension -RUN pecl install protobuf && docker-php-ext-enable protobuf - -# Install OpenTelemetry extension -RUN pecl install opentelemetry && docker-php-ext-enable opentelemetry - -# Install sockets extension RUN docker-php-ext-install sockets -# Install Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -# Set working directory WORKDIR /app -# Copy composer files first for better caching COPY composer.json composer.lock ./ - -# Install dependencies RUN composer install --no-scripts --no-autoloader --prefer-dist - -# Copy the rest of the application COPY . . - -# Generate autoloader -RUN composer dump-autoload --optimize - -# Default command -CMD ["php", "-v"] +CMD ["tail", "-f", "/dev/null"] diff --git a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php index ca24818..ba7b69d 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php @@ -14,10 +14,8 @@ use Swoole\Coroutine\Http\Client; /** - * High-performance Swoole coroutine-native HTTP transport for OpenTelemetry. - * + * Swoole coroutine-native HTTP transport for OpenTelemetry. * Uses connection pooling with keep-alive for maximum throughput. - * Designed for Swoole's coroutine scheduler without cURL multi-handle conflicts. * * @implements TransportInterface */ @@ -52,6 +50,10 @@ public function __construct( int $socketBufferSize = 64 * 1024, // 64 KB ) { $parsed = parse_url($endpoint); + if ($parsed === false) { + throw new \InvalidArgumentException("Invalid endpoint URL: {$endpoint}"); + } + $this->ssl = ($parsed['scheme'] ?? 'http') === 'https'; $this->host = $parsed['host'] ?? 'localhost'; $this->port = $parsed['port'] ?? ($this->ssl ? 443 : 80); @@ -97,42 +99,6 @@ public function contentType(): string return $this->contentType; } - /** - * Acquire a client from the pool or create a new one. - */ - private function popClient(): Client - { - $client = $this->pool->pop(0.001); - if ($client === false) { - $client = $this->pool->pop(0.05); - } - if ($client instanceof Client) { - if ($client->connected) { - return $client; - } - $client->close(); - } - - $client = new Client($this->host, $this->port, $this->ssl); - $client->set($this->settings); - - return $client; - } - - /** - * Return a client to the pool or close it. - */ - private function putClient(Client $client, bool $forceClose = false): void - { - if ($this->shutdown->get() === 1 || $forceClose || !$client->connected) { - $client->close(); - return; - } - if (!$this->pool->push($client, 1.0)) { - $client->close(); - } - } - /** * @return FutureInterface */ @@ -157,7 +123,7 @@ public function send(string $payload, ?CancellationInterface $cancellation = nul if ($statusCode < 0) { $forceClose = true; $errCode = \is_int($client->errCode) ? $client->errCode : 0; - $errMsg = \socket_strerror($errCode); + $errMsg = $client->errMsg ?? 'Unknown error'; return new ErrorFuture(new Exception("OTLP connection failed: {$errMsg} (code: {$errCode})")); } @@ -187,6 +153,42 @@ public function send(string $payload, ?CancellationInterface $cancellation = nul } } + /** + * Acquire a client from the pool or create a new one. + */ + private function popClient(): Client + { + $client = $this->pool->pop(0.001); + if ($client === false) { + $client = $this->pool->pop(0.05); + } + if ($client instanceof Client) { + if ($client->connected) { + return $client; + } + $client->close(); + } + + $client = new Client($this->host, $this->port, $this->ssl); + $client->set($this->settings); + + return $client; + } + + /** + * Return a client to the pool or close it. + */ + private function putClient(Client $client, bool $forceClose = false): void + { + if ($this->shutdown->get() === 1 || $forceClose || !$client->connected) { + $client->close(); + return; + } + if (!$this->pool->push($client, 1.0)) { + $client->close(); + } + } + public function shutdown(?CancellationInterface $cancellation = null): bool { $this->shutdown->set(1); diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php index 7ae09de..d9640af 100644 --- a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/MockOtlpServer.php @@ -2,12 +2,15 @@ namespace Tests\Telemetry\Adapter\OpenTelemetry\Swoole; +use Swoole\Coroutine; use Swoole\Coroutine\Http\Client; use Swoole\Coroutine\Http\Server; use Swoole\Http\Request; use Swoole\Http\Response; +use Utopia\Telemetry\Exception; use function Swoole\Coroutine\go; +use function Swoole\Coroutine\run; /** * Mock OTLP server for integration testing. @@ -91,7 +94,7 @@ public function start(): void ]; if ($this->responseDelay > 0) { - \Swoole\Coroutine::sleep($this->responseDelay); + Coroutine::sleep($this->responseDelay); } $response->status($this->statusCode); @@ -113,6 +116,8 @@ public function stop(): void /** * Wait for the server to be ready to accept connections. + * + * @throws Exception If the server fails to start within the timeout period */ private function waitUntilReady(float $timeout = 2.0): void { @@ -127,8 +132,15 @@ private function waitUntilReady(float $timeout = 2.0): void return; } $client->close(); - \Swoole\Coroutine::sleep(0.01); + Coroutine::sleep(0.01); } + + throw new Exception(sprintf( + 'MockOtlpServer failed to start: could not connect to %s:%d within %.1f seconds', + self::HOST, + $this->port, + $timeout + )); } /** @@ -191,11 +203,10 @@ public static function run(callable $test): void } }; - // Check if already in coroutine context - if (\Swoole\Coroutine::getCid() > 0) { + if (Coroutine::getCid() > 0) { $executor(); } else { - \Swoole\Coroutine\run($executor); + run($executor); } if ($exception !== null) { diff --git a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php index 33f71b3..08682e7 100644 --- a/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php +++ b/tests/Telemetry/Adapter/OpenTelemetry/Swoole/TransportTest.php @@ -147,9 +147,13 @@ public function testSendAfterShutdownReturnsError(): void $future = $transport->send('test payload'); - $this->expectException(Exception::class); - $this->expectExceptionMessage('Transport has been shut down'); - $future->await(); + try { + $future->await(); + $this->fail('Expected exception was not thrown'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Transport has been shut down', $e->getMessage()); + } }); } @@ -173,12 +177,12 @@ public function testEndpointWithQueryString(): void $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); } - public function testEndpointDefaultsToLocalhost(): void + public function testMalformedUrlThrowsException(): void { - // Malformed URL should still create transport (defaults to localhost) - $transport = new Swoole('http:///v1/metrics'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid endpoint URL'); - $this->assertEquals(ContentTypes::PROTOBUF, $transport->contentType()); + new Swoole('http:///v1/metrics'); } public function testDefaultPortForHttp(): void From ea1f9e0c85c7f8ff2f705d01115e001c7ea2cd36 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 18:52:38 +1300 Subject: [PATCH 14/18] Fix tests --- composer.lock | 127 +++++++++++++++--- .../OpenTelemetry/Transport/Swoole.php | 2 +- 2 files changed, 107 insertions(+), 22 deletions(-) diff --git a/composer.lock b/composer.lock index d0acabd..557d4e3 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": "1682111617e78148dc4167a1359ac72d", + "content-hash": "0d94b4c877d8d2acc33c4ff2fc91c560", "packages": [ { "name": "brick/math", @@ -1306,27 +1306,31 @@ }, { "name": "symfony/http-client", - "version": "v8.0.1", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0" + "reference": "26cc224ea7103dda90e9694d9e139a389092d007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0", - "reference": "727fda60d0aebfdfcc4c8bc4661f0cb8f44153c0", + "url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007", + "reference": "26cc224ea7103dda90e9694d9e139a389092d007", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.2", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "amphp/amp": "<3", - "php-http/discovery": "<1.15" + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -1335,19 +1339,20 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/http-client": "^5.3.2", - "amphp/http-tunnel": "^2.0", + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/cache": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/rate-limiter": "^7.4|^8.0", - "symfony/stopwatch": "^7.4|^8.0" + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1378,7 +1383,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v8.0.1" + "source": "https://github.com/symfony/http-client/tree/v7.4.1" }, "funding": [ { @@ -1398,7 +1403,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T14:08:45+00:00" + "time": "2025-12-04T21:12:57+00:00" }, { "name": "symfony/http-client-contracts", @@ -1643,6 +1648,86 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.1", @@ -4876,7 +4961,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4884,6 +4969,6 @@ "ext-opentelemetry": "*", "php": ">=8.0" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.9.0" } diff --git a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php index ba7b69d..67c5b11 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php @@ -123,7 +123,7 @@ public function send(string $payload, ?CancellationInterface $cancellation = nul if ($statusCode < 0) { $forceClose = true; $errCode = \is_int($client->errCode) ? $client->errCode : 0; - $errMsg = $client->errMsg ?? 'Unknown error'; + $errMsg = \is_string($client->errMsg) ? $client->errMsg : 'Unknown error'; return new ErrorFuture(new Exception("OTLP connection failed: {$errMsg} (code: {$errCode})")); } From 1285ab5a8e732a496e4b39f6cbac833d9935dfc6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 19:04:01 +1300 Subject: [PATCH 15/18] Flip case --- src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php index 67c5b11..2530a6f 100644 --- a/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php +++ b/src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php @@ -75,9 +75,9 @@ public function __construct( ); $this->settings = [ 'timeout' => $this->timeout, - 'connect_timeout' => \min(1.0, $this->timeout), - 'read_timeout' => \min(1.0, $this->timeout), - 'write_timeout' => \min(1.0, $this->timeout), + 'connect_timeout' => \max(0.5, $this->timeout), + 'read_timeout' => \max(1.0, $this->timeout), + 'write_timeout' => \max(1.0, $this->timeout), 'keep_alive' => true, 'open_tcp_nodelay' => true, 'open_tcp_keepalive' => true, From 21efcada1ebd94e02789474baffbb900fd0d1c37 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 19:07:40 +1300 Subject: [PATCH 16/18] Fix test workflow --- .github/workflows/tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 649505d..9567f51 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,4 +20,9 @@ jobs: uses: actions/checkout@v4 - name: Run tests - run: PHP_VERSION=${{ matrix.php }} docker compose run --rm tests composer test \ No newline at end of file + run: | + PHP_VERSION=${{ matrix.php }} docker compose run --rm tests sh -c " + git config --global --add safe.directory /app && + composer install --no-interaction && + composer test + " \ No newline at end of file From 581432bcb6792e6e1d4783414ec34f2013881f3d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 19:27:16 +1300 Subject: [PATCH 17/18] Fix versions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9567f51..deb7326 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,6 +23,6 @@ jobs: run: | PHP_VERSION=${{ matrix.php }} docker compose run --rm tests sh -c " git config --global --add safe.directory /app && - composer install --no-interaction && + composer update --no-interaction && composer test " \ No newline at end of file From 9367bc359b30c0acdbd5564df16ea250f44563d7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 17 Dec 2025 20:03:08 +1300 Subject: [PATCH 18/18] Fix versions --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 43fe46a..6c9c050 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,6 +20,6 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer WORKDIR /app COPY composer.json composer.lock ./ -RUN composer install --no-scripts --no-autoloader --prefer-dist +RUN composer install --no-scripts --no-autoloader --prefer-dist --ignore-platform-reqs COPY . . CMD ["tail", "-f", "/dev/null"]