From 6994ec3e55a74fef2b25ac6aa179c09a1cc9ac3e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 16 Jun 2026 08:19:42 +0530 Subject: [PATCH 1/3] Add messaging send telemetry --- composer.json | 7 +- composer.lock | 2017 ++++++++++++++++- src/Utopia/Messaging/Adapter.php | 76 +- src/Utopia/Messaging/Adapter/Chat/Discord.php | 1 + .../Messaging/Adapter/Email/Mailgun.php | 1 + src/Utopia/Messaging/Adapter/Email/Resend.php | 1 + src/Utopia/Messaging/Adapter/Email/SES.php | 1 + src/Utopia/Messaging/Adapter/Email/SMTP.php | 1 + .../Messaging/Adapter/Email/Sendgrid.php | 1 + src/Utopia/Messaging/Adapter/Push/APNS.php | 1 + src/Utopia/Messaging/Adapter/Push/FCM.php | 1 + .../Messaging/Adapter/SMS/Clickatell.php | 1 + src/Utopia/Messaging/Adapter/SMS/Fast2SMS.php | 1 + src/Utopia/Messaging/Adapter/SMS/GEOSMS.php | 5 +- src/Utopia/Messaging/Adapter/SMS/Infobip.php | 1 + src/Utopia/Messaging/Adapter/SMS/Inforu.php | 1 + src/Utopia/Messaging/Adapter/SMS/Mock.php | 1 + src/Utopia/Messaging/Adapter/SMS/Msg91.php | 1 + src/Utopia/Messaging/Adapter/SMS/Plivo.php | 1 + src/Utopia/Messaging/Adapter/SMS/Seven.php | 1 + src/Utopia/Messaging/Adapter/SMS/Sinch.php | 1 + src/Utopia/Messaging/Adapter/SMS/Telesign.php | 1 + src/Utopia/Messaging/Adapter/SMS/Telnyx.php | 1 + .../Messaging/Adapter/SMS/TextMagic.php | 1 + src/Utopia/Messaging/Adapter/SMS/Twilio.php | 1 + src/Utopia/Messaging/Adapter/SMS/Vonage.php | 1 + src/Utopia/Messaging/Messages/Discord.php | 14 + src/Utopia/Messaging/Messages/Email.php | 14 + src/Utopia/Messaging/Messages/Push.php | 14 + src/Utopia/Messaging/Messages/SMS.php | 14 + tests/Messaging/Adapter/TelemetryTest.php | 171 ++ 31 files changed, 2260 insertions(+), 94 deletions(-) create mode 100644 tests/Messaging/Adapter/TelemetryTest.php diff --git a/composer.json b/composer.json index db7c1ff3..261c6cc1 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "ext-curl": "*", "ext-openssl": "*", "phpmailer/phpmailer": "6.9.1", - "giggsey/libphonenumber-for-php-lite": "9.0.23" + "giggsey/libphonenumber-for-php-lite": "9.0.23", + "utopia-php/telemetry": "^0.4" }, "require-dev": { "phpunit/phpunit": "11.*", @@ -36,6 +37,10 @@ "config": { "platform": { "php": "8.3" + }, + "allow-plugins": { + "php-http/discovery": true, + "tbachert/spi": true } } } diff --git a/composer.lock b/composer.lock index b7856622..ba1f9046 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,145 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fb98182a4b49a3d30c785a6e888722b3", + "content-hash": "94123f2a566774952009659b7e6f241f", "packages": [ + { + "name": "brick/math", + "version": "0.14.8", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.8" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2026-02-10T14:33:43+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "giggsey/libphonenumber-for-php-lite", "version": "9.0.23", @@ -85,123 +222,155 @@ "time": "2026-01-30T12:33:03+00:00" }, { - "name": "phpmailer/phpmailer", - "version": "v6.9.1", + "name": "google/protobuf", + "version": "v4.33.6", "source": { "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "84b008c23915ed94536737eae46f41ba3bccfe67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/84b008c23915ed94536737eae46f41ba3bccfe67", + "reference": "84b008c23915ed94536737eae46f41ba3bccfe67", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" + "php": ">=8.1.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "doctrine/annotations": "^1.2.6 || ^1.13.3", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.2", - "yoast/phpunit-polyfills": "^1.0.4" + "phpunit/phpunit": ">=10.5.62 <11.0.0" }, "suggest": { - "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "ext-openssl": "Needed for secure SMTP sending and DKIM signing", - "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + "ext-bcmath": "Need to support JSON deserialization" }, "type": "library", "autoload": { "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1-only" + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.6" + }, + "time": "2026-03-18T17:32:05+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], "authors": [ { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" }, { - "name": "Brent R. Matzelle" + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" } ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], "support": { - "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { - "url": "https://github.com/Synchro", + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", "type": "github" } ], - "time": "2023-11-25T22:23:28+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "name": "nyholm/psr7-server", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Nyholm\\Psr7Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -210,45 +379,1713 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "psr-17", + "psr-7" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/Zegnat", "type": "github" }, { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/nyholm", "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "open-telemetry/api", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/6f8d237ce2c304ca85f31970f788e7f074d147be", + "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.4", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.11" + }, + "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] }, + "branch-alias": { + "dev-main": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "time": "2024-12-23T08:48:59+00:00" + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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": "2026-02-25T13:24:05+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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-10-19T06:44:33+00:00" + }, + { + "name": "open-telemetry/exporter-otlp", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/exporter-otlp.git", + "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/283a0d66522f2adc6d8d7debfd7686be91c282be", + "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be", + "shasum": "" + }, + "require": { + "open-telemetry/api": "^1.0", + "open-telemetry/gen-otlp-protobuf": "^1.1", + "open-telemetry/sdk": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "_register.php" + ], + "psr-4": { + "OpenTelemetry\\Contrib\\Otlp\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "OTLP exporter for OpenTelemetry.", + "keywords": [ + "Metrics", + "exporter", + "gRPC", + "http", + "opentelemetry", + "otel", + "otlp", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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": "2026-02-05T09:44:52+00:00" + }, + { + "name": "open-telemetry/gen-otlp-protobuf", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", + "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/a229cf161d42001d64c8f21e8f678581fe1c66b9", + "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9", + "shasum": "" + }, + "require": { + "google/protobuf": "^3.22 || ^4.0", + "php": "^8.0" + }, + "suggest": { + "ext-protobuf": "For better performance, when dealing with the protobuf format" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opentelemetry\\Proto\\": "Opentelemetry/Proto/", + "GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.", + "keywords": [ + "Metrics", + "apm", + "gRPC", + "logging", + "opentelemetry", + "otel", + "otlp", + "protobuf", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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-10-19T06:44:33+00:00" + }, + { + "name": "open-telemetry/sdk", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/6e3d0ce93e76555dd5e2f1d19443ff45b990e410", + "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "^1.8", + "open-telemetry/context": "^1.4", + "open-telemetry/sem-conv": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.5" + }, + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" + }, + "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.12.x-dev" + } + }, + "autoload": { + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "SDK for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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": "2026-03-21T11:50:01+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.38.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "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": "2026-01-21T04:14:03+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2023-11-25T22:23:28+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "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" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.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.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.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.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": "2026-04-13T15:52:40+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "e8a112b8415707265a7e614278136a9d92989a6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/e8a112b8415707265a7e614278136a9d92989a6a", + "reference": "e8a112b8415707265a7e614278136a9d92989a6a", + "shasum": "" + }, + "require": { + "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": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "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/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": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.13" + }, + "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": "2026-05-24T09:57:54+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.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": "2026-03-06T13:17:50+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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 for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "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": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "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\\Php82\\": "" + }, + "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.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + }, + "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": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "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.38.2" + }, + "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": "2026-05-27T06:51:48+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.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": "2026-03-28T09:44:51+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.5" + }, + "time": "2025-06-29T15:42:06+00:00" + }, + { + "name": "utopia-php/telemetry", + "version": "0.4.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/telemetry.git", + "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/028f3a85bb987b1e9357fb1632aa40f020d1b86e", + "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e", + "shasum": "" + }, + "require": { + "ext-opentelemetry": "*", + "ext-protobuf": "*", + "nyholm/psr7": "1.*", + "open-telemetry/exporter-otlp": "1.*", + "open-telemetry/sdk": "1.*", + "php": ">=8.0", + "symfony/http-client": "7.*" + }, + "require-dev": { + "phpbench/phpbench": "1.*", + "swoole/ide-helper": "6.*" + }, + "suggest": { + "ext-sockets": "Required for the Swoole transport implementation", + "ext-swoole": "Required for the Swoole transport implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Telemetry\\": "src/Telemetry" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A lite & fast telemetry library, with adapters for OpenTelemetry", + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/telemetry/issues", + "source": "https://github.com/utopia-php/telemetry/tree/0.4.1" + }, + "time": "2026-06-10T15:48:26+00:00" } ], "packages-dev": [ diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index a631a14f..61af09be 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -4,9 +4,19 @@ use Exception; use libphonenumber\PhoneNumberUtil; +use Utopia\Telemetry\Adapter as Telemetry; +use Utopia\Telemetry\Adapter\None as NoTelemetry; +use Utopia\Telemetry\Counter; abstract class Adapter { + private Counter $sendCounter; + + public function __construct(?Telemetry $telemetry = null) + { + $this->setTelemetry($telemetry ?? new NoTelemetry()); + } + /** * Get the name of the adapter. */ @@ -54,7 +64,71 @@ public function send(Message $message): array throw new \Exception('Adapter does not implement process method.'); } - return $this->process($message); + try { + $response = $this->process($message); + } catch (\Throwable $error) { + $this->recordSend($message, \method_exists($message, 'getTo') ? \count($message->getTo()) : 1, 0); + throw $error; + } + + $this->recordResponse($message, $response); + + return $response; + } + + public function setTelemetry(Telemetry $telemetry): void + { + $this->sendCounter = $telemetry->createCounter('messaging.send'); + } + + private function recordSend(Message $message, int $recipients, int $delivered): void + { + if ($delivered > 0) { + $this->sendCounter->add($delivered, $this->telemetryAttributes($message, [ + 'result' => 'success', + ])); + } + + $failed = $recipients - $delivered; + if ($failed > 0) { + $this->sendCounter->add($failed, $this->telemetryAttributes($message, [ + 'result' => 'failure', + ])); + } + } + + /** + * @param array $attributes + * @return array + */ + private function telemetryAttributes(Message $message, array $attributes = []): array + { + if (\method_exists($message, 'getOrigin') && $message->getOrigin() !== null) { + $attributes['origin'] = $message->getOrigin(); + } + + return $attributes + [ + 'type' => $this->getType(), + 'provider' => \strtolower($this->getName()), + ]; + } + + /** + * @param array $response + */ + private function recordResponse(Message $message, array $response): void + { + if (!isset($response['deliveredTo'])) { + return; + } + + $delivered = (int) $response['deliveredTo']; + $failed = \count(\array_filter( + $response['results'] ?? [], + fn (array $result): bool => ($result['status'] ?? '') === 'failure' + )); + + $this->recordSend($message, $delivered + $failed, $delivered); } /** diff --git a/src/Utopia/Messaging/Adapter/Chat/Discord.php b/src/Utopia/Messaging/Adapter/Chat/Discord.php index 73bf5a82..d0f76ed3 100644 --- a/src/Utopia/Messaging/Adapter/Chat/Discord.php +++ b/src/Utopia/Messaging/Adapter/Chat/Discord.php @@ -21,6 +21,7 @@ class Discord extends Adapter public function __construct( private string $webhookURL ) { + parent::__construct(); // Validate URL format if (!filter_var($webhookURL, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('Invalid Discord webhook URL format.'); diff --git a/src/Utopia/Messaging/Adapter/Email/Mailgun.php b/src/Utopia/Messaging/Adapter/Email/Mailgun.php index 63933f2e..c166f7b1 100644 --- a/src/Utopia/Messaging/Adapter/Email/Mailgun.php +++ b/src/Utopia/Messaging/Adapter/Email/Mailgun.php @@ -19,6 +19,7 @@ public function __construct( private string $domain, private bool $isEU = false ) { + parent::__construct(); } /** diff --git a/src/Utopia/Messaging/Adapter/Email/Resend.php b/src/Utopia/Messaging/Adapter/Email/Resend.php index b1a799a1..3624269c 100644 --- a/src/Utopia/Messaging/Adapter/Email/Resend.php +++ b/src/Utopia/Messaging/Adapter/Email/Resend.php @@ -18,6 +18,7 @@ class Resend extends EmailAdapter public function __construct( private string $apiKey ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/Email/SES.php b/src/Utopia/Messaging/Adapter/Email/SES.php index 17ce0278..18565938 100644 --- a/src/Utopia/Messaging/Adapter/Email/SES.php +++ b/src/Utopia/Messaging/Adapter/Email/SES.php @@ -103,6 +103,7 @@ public function __construct( private string $region, private ?string $sessionToken = null, ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/Email/SMTP.php b/src/Utopia/Messaging/Adapter/Email/SMTP.php index 7179f505..02a09bab 100644 --- a/src/Utopia/Messaging/Adapter/Email/SMTP.php +++ b/src/Utopia/Messaging/Adapter/Email/SMTP.php @@ -35,6 +35,7 @@ public function __construct( private bool $keepAlive = false, private int $timelimit = 30, ) { + parent::__construct(); if (!\in_array($this->smtpSecure, ['', 'ssl', 'tls'])) { throw new \InvalidArgumentException('Invalid SMTP secure prefix. Must be "", "ssl" or "tls"'); } diff --git a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php index 7b5620df..4b1fd92a 100644 --- a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php +++ b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php @@ -16,6 +16,7 @@ class Sendgrid extends EmailAdapter */ public function __construct(private string $apiKey) { + parent::__construct(); } /** diff --git a/src/Utopia/Messaging/Adapter/Push/APNS.php b/src/Utopia/Messaging/Adapter/Push/APNS.php index b30baa13..2e6e4b45 100644 --- a/src/Utopia/Messaging/Adapter/Push/APNS.php +++ b/src/Utopia/Messaging/Adapter/Push/APNS.php @@ -22,6 +22,7 @@ public function __construct( private string $bundleId, private bool $sandbox = false ) { + parent::__construct(); } /** diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index d3156600..49866bd8 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -21,6 +21,7 @@ class FCM extends PushAdapter public function __construct( private string $serviceAccountJSON, ) { + parent::__construct(); } /** diff --git a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php index 7bbcee02..fd8d9a2b 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php +++ b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php @@ -19,6 +19,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Fast2SMS.php b/src/Utopia/Messaging/Adapter/SMS/Fast2SMS.php index 296e1f53..e4378102 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Fast2SMS.php +++ b/src/Utopia/Messaging/Adapter/SMS/Fast2SMS.php @@ -35,6 +35,7 @@ public function __construct( private string $messageId = '', private bool $useDLT = false ) { + parent::__construct(); } /** diff --git a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php index b1955ca5..956e701a 100644 --- a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php +++ b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php @@ -19,6 +19,7 @@ class GEOSMS extends SMSAdapter public function __construct(SMSAdapter $defaultAdapter) { + parent::__construct(); $this->defaultAdapter = $defaultAdapter; } @@ -68,12 +69,12 @@ protected function process(SMS $message): array try { $results[$nextAdapter->getName()] = $nextAdapter->send( - new SMS( + (new SMS( to: $nextRecipients, content: $message->getContent(), from: $message->getFrom(), attachments: $message->getAttachments() - ) + ))->setOrigin($message->getOrigin()) ); } catch (\Exception $e) { $results[$nextAdapter->getName()] = [ diff --git a/src/Utopia/Messaging/Adapter/SMS/Infobip.php b/src/Utopia/Messaging/Adapter/SMS/Infobip.php index 4eac6d7e..25d61367 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Infobip.php +++ b/src/Utopia/Messaging/Adapter/SMS/Infobip.php @@ -21,6 +21,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Inforu.php b/src/Utopia/Messaging/Adapter/SMS/Inforu.php index 54b8dcc5..f97bb4d9 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Inforu.php +++ b/src/Utopia/Messaging/Adapter/SMS/Inforu.php @@ -20,6 +20,7 @@ public function __construct( private string $senderId, private string $apiToken, ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Mock.php b/src/Utopia/Messaging/Adapter/SMS/Mock.php index ee141604..2f9d72e5 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Mock.php +++ b/src/Utopia/Messaging/Adapter/SMS/Mock.php @@ -20,6 +20,7 @@ public function __construct( private string $user, private string $secret ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Msg91.php b/src/Utopia/Messaging/Adapter/SMS/Msg91.php index 850e7747..8c77907c 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Msg91.php +++ b/src/Utopia/Messaging/Adapter/SMS/Msg91.php @@ -23,6 +23,7 @@ public function __construct( private string $authKey, private string $templateId, ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Plivo.php b/src/Utopia/Messaging/Adapter/SMS/Plivo.php index 4785aca1..67e6aff7 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Plivo.php +++ b/src/Utopia/Messaging/Adapter/SMS/Plivo.php @@ -21,6 +21,7 @@ public function __construct( private string $authToken, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Seven.php b/src/Utopia/Messaging/Adapter/SMS/Seven.php index f4d58818..2f0569c9 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Seven.php +++ b/src/Utopia/Messaging/Adapter/SMS/Seven.php @@ -19,6 +19,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Sinch.php b/src/Utopia/Messaging/Adapter/SMS/Sinch.php index 04fcf36e..bd891459 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Sinch.php +++ b/src/Utopia/Messaging/Adapter/SMS/Sinch.php @@ -21,6 +21,7 @@ public function __construct( private string $apiToken, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Telesign.php b/src/Utopia/Messaging/Adapter/SMS/Telesign.php index d5d33492..17a2bf7a 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telesign.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telesign.php @@ -21,6 +21,7 @@ public function __construct( private string $customerId, private string $apiKey ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php index 4586c111..1896ae88 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php @@ -17,6 +17,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php index 0f37518f..a713482b 100644 --- a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php +++ b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php @@ -22,6 +22,7 @@ public function __construct( private string $apiKey, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Twilio.php b/src/Utopia/Messaging/Adapter/SMS/Twilio.php index e5151969..5b5b638b 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Twilio.php +++ b/src/Utopia/Messaging/Adapter/SMS/Twilio.php @@ -20,6 +20,7 @@ public function __construct( private ?string $from = null, private ?string $messagingServiceSid = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Adapter/SMS/Vonage.php b/src/Utopia/Messaging/Adapter/SMS/Vonage.php index d7644386..d583104c 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Vonage.php +++ b/src/Utopia/Messaging/Adapter/SMS/Vonage.php @@ -22,6 +22,7 @@ public function __construct( private string $apiSecret, private ?string $from = null ) { + parent::__construct(); } public function getName(): string diff --git a/src/Utopia/Messaging/Messages/Discord.php b/src/Utopia/Messaging/Messages/Discord.php index dffa5fec..18c8c3ec 100644 --- a/src/Utopia/Messaging/Messages/Discord.php +++ b/src/Utopia/Messaging/Messages/Discord.php @@ -6,6 +6,8 @@ class Discord implements Message { + private ?string $origin = null; + /** * @param array|null $embeds * @param array|null $allowedMentions @@ -99,4 +101,16 @@ public function getThreadId(): ?string { return $this->threadId; } + + public function setOrigin(?string $origin): self + { + $this->origin = $origin; + + return $this; + } + + public function getOrigin(): ?string + { + return $this->origin; + } } diff --git a/src/Utopia/Messaging/Messages/Email.php b/src/Utopia/Messaging/Messages/Email.php index 877394a4..069377d0 100644 --- a/src/Utopia/Messaging/Messages/Email.php +++ b/src/Utopia/Messaging/Messages/Email.php @@ -7,6 +7,8 @@ class Email implements Message { + private ?string $origin = null; + /** * @var array> */ @@ -150,4 +152,16 @@ public function isHtml(): bool { return $this->html; } + + public function setOrigin(?string $origin): self + { + $this->origin = $origin; + + return $this; + } + + public function getOrigin(): ?string + { + return $this->origin; + } } diff --git a/src/Utopia/Messaging/Messages/Push.php b/src/Utopia/Messaging/Messages/Push.php index c32fbee6..fe547057 100644 --- a/src/Utopia/Messaging/Messages/Push.php +++ b/src/Utopia/Messaging/Messages/Push.php @@ -7,6 +7,8 @@ class Push implements Message { + private ?string $origin = null; + /** * @param array $to The recipients of the push notification. * @param string|null $title The title of the push notification. @@ -128,4 +130,16 @@ public function getPriority(): ?Priority { return $this->priority; } + + public function setOrigin(?string $origin): self + { + $this->origin = $origin; + + return $this; + } + + public function getOrigin(): ?string + { + return $this->origin; + } } diff --git a/src/Utopia/Messaging/Messages/SMS.php b/src/Utopia/Messaging/Messages/SMS.php index a2eb2bad..d39d7de6 100644 --- a/src/Utopia/Messaging/Messages/SMS.php +++ b/src/Utopia/Messaging/Messages/SMS.php @@ -6,6 +6,8 @@ class SMS implements Message { + private ?string $origin = null; + /** * @param array $to * @param array|null $attachments @@ -43,4 +45,16 @@ public function getAttachments(): ?array { return $this->attachments; } + + public function setOrigin(?string $origin): self + { + $this->origin = $origin; + + return $this; + } + + public function getOrigin(): ?string + { + return $this->origin; + } } diff --git a/tests/Messaging/Adapter/TelemetryTest.php b/tests/Messaging/Adapter/TelemetryTest.php new file mode 100644 index 00000000..d72bb93c --- /dev/null +++ b/tests/Messaging/Adapter/TelemetryTest.php @@ -0,0 +1,171 @@ + 2, + 'type' => 'sms', + 'results' => [ + ['recipient' => '+1', 'status' => 'success', 'error' => ''], + ['recipient' => '+2', 'status' => 'success', 'error' => ''], + ['recipient' => '+3', 'status' => 'failure', 'error' => 'Nope'], + ], + ]); + $adapter->setTelemetry($telemetry); + + $adapter->send((new SMS(['+1', '+2', '+3'], 'Hello'))->setOrigin('external')); + + $this->assertSame([ + ['amount' => 2, 'attributes' => ['result' => 'success', 'origin' => 'external', 'type' => 'sms', 'provider' => 'test']], + ['amount' => 1, 'attributes' => ['result' => 'failure', 'origin' => 'external', 'type' => 'sms', 'provider' => 'test']], + ], $telemetry->records); + } + + public function testRecordsThrownSendAsFailure(): void + { + $telemetry = new RecordingTelemetry(); + $adapter = new TelemetryAdapter(null, new \Exception('Provider failed')); + $adapter->setTelemetry($telemetry); + + $this->expectException(\Exception::class); + + try { + $adapter->send(new SMS(['+1', '+2'], 'Hello')); + } finally { + $this->assertSame([ + ['amount' => 2, 'attributes' => ['result' => 'failure', 'type' => 'sms', 'provider' => 'test']], + ], $telemetry->records); + } + } + + public function testDefaultTelemetryDoesNothing(): void + { + $adapter = new TelemetryAdapter([ + 'deliveredTo' => 1, + 'type' => 'sms', + 'results' => [ + ['recipient' => '+1', 'status' => 'success', 'error' => ''], + ], + ]); + + $response = $adapter->send(new SMS(['+1'], 'Hello')); + + $this->assertSame(1, $response['deliveredTo']); + } +} + +class TelemetryAdapter extends Adapter +{ + /** + * @param array|null $response + */ + public function __construct( + private ?array $response = null, + private ?\Throwable $error = null, + ) { + parent::__construct(); + } + + public function getName(): string + { + return 'Test'; + } + + public function getType(): string + { + return 'sms'; + } + + public function getMessageType(): string + { + return SMS::class; + } + + public function getMaxMessagesPerRequest(): int + { + return 100; + } + + /** + * @return array + */ + protected function process(Message $message): array + { + if ($this->error !== null) { + throw $this->error; + } + + return $this->response ?? [ + 'deliveredTo' => 0, + 'type' => 'sms', + 'results' => [], + ]; + } +} + +class RecordingTelemetry implements Telemetry +{ + /** + * @var array}> + */ + public array $records = []; + + public function createCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Counter + { + return new class ($this) extends Counter { + public function __construct(private RecordingTelemetry $telemetry) + { + } + + public function add(float|int $amount, iterable $attributes = []): void + { + $this->telemetry->records[] = [ + 'amount' => $amount, + 'attributes' => \iterator_to_array((function () use ($attributes) { + yield from $attributes; + })()), + ]; + } + }; + } + + public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Histogram + { + throw new \BadMethodCallException(); + } + + public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): Gauge + { + throw new \BadMethodCallException(); + } + + public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounter + { + throw new \BadMethodCallException(); + } + + public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): ObservableGauge + { + throw new \BadMethodCallException(); + } + + public function collect(): bool + { + return true; + } +} From 87d8bc4c6fa4073bcbb9f01c63c852b09ffe09e8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 16 Jun 2026 08:32:17 +0530 Subject: [PATCH 2/3] Address telemetry review feedback --- src/Utopia/Messaging/Adapter.php | 16 ++--- src/Utopia/Messaging/Adapter/SMS/GEOSMS.php | 21 ++++++- tests/Messaging/Adapter/TelemetryTest.php | 66 ++++++++++++++++----- 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index 61af09be..f8013112 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -14,7 +14,7 @@ abstract class Adapter public function __construct(?Telemetry $telemetry = null) { - $this->setTelemetry($telemetry ?? new NoTelemetry()); + $this->sendCounter = ($telemetry ?? new NoTelemetry())->createCounter('messaging.send'); } /** @@ -118,15 +118,17 @@ private function telemetryAttributes(Message $message, array $attributes = []): */ private function recordResponse(Message $message, array $response): void { - if (!isset($response['deliveredTo'])) { + $results = $response['results'] ?? []; + if (empty($results)) { return; } - $delivered = (int) $response['deliveredTo']; - $failed = \count(\array_filter( - $response['results'] ?? [], - fn (array $result): bool => ($result['status'] ?? '') === 'failure' - )); + $delivered = 0; + $failed = 0; + + foreach ($results as $result) { + ($result['status'] ?? '') === 'success' ? $delivered++ : $failed++; + } $this->recordSend($message, $delivered + $failed, $delivered); } diff --git a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php index 956e701a..b3ec5c19 100644 --- a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php +++ b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php @@ -5,6 +5,8 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Adapter\SMS\GEOSMS\CallingCode; use Utopia\Messaging\Messages\SMS; +use Utopia\Telemetry\Adapter as Telemetry; +use Utopia\Telemetry\Adapter\None as NoTelemetry; class GEOSMS extends SMSAdapter { @@ -12,6 +14,8 @@ class GEOSMS extends SMSAdapter protected SMSAdapter $defaultAdapter; + private Telemetry $telemetry; + /** * @var array */ @@ -19,8 +23,10 @@ class GEOSMS extends SMSAdapter public function __construct(SMSAdapter $defaultAdapter) { - parent::__construct(); + $this->telemetry = new NoTelemetry(); + parent::__construct($this->telemetry); $this->defaultAdapter = $defaultAdapter; + $this->defaultAdapter->setTelemetry($this->telemetry); } public function getName(): string @@ -36,10 +42,23 @@ public function getMaxMessagesPerRequest(): int public function setLocal(string $callingCode, SMSAdapter $adapter): self { $this->localAdapters[$callingCode] = $adapter; + $adapter->setTelemetry($this->telemetry); return $this; } + public function setTelemetry(Telemetry $telemetry): void + { + $this->telemetry = $telemetry; + parent::setTelemetry($telemetry); + + $this->defaultAdapter->setTelemetry($telemetry); + + foreach ($this->localAdapters as $adapter) { + $adapter->setTelemetry($telemetry); + } + } + /** * @return array */ diff --git a/tests/Messaging/Adapter/TelemetryTest.php b/tests/Messaging/Adapter/TelemetryTest.php index d72bb93c..a5035aaa 100644 --- a/tests/Messaging/Adapter/TelemetryTest.php +++ b/tests/Messaging/Adapter/TelemetryTest.php @@ -2,8 +2,9 @@ namespace Utopia\Tests\Adapter; -use Utopia\Messaging\Adapter; -use Utopia\Messaging\Message; +use Utopia\Messaging\Adapter\SMS\GEOSMS; +use Utopia\Messaging\Adapter\SMS\GEOSMS\CallingCode; +use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS; use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Counter; @@ -53,6 +54,53 @@ public function testRecordsThrownSendAsFailure(): void } } + public function testRecordsCountsFromResults(): void + { + $telemetry = new RecordingTelemetry(); + $adapter = new TelemetryAdapter([ + 'deliveredTo' => 99, + 'type' => 'sms', + 'results' => [ + ['recipient' => '+1', 'status' => 'success', 'error' => ''], + ['recipient' => '+2', 'status' => 'pending', 'error' => ''], + ], + ]); + $adapter->setTelemetry($telemetry); + + $adapter->send(new SMS(['+1', '+2'], 'Hello')); + + $this->assertSame([ + ['amount' => 1, 'attributes' => ['result' => 'success', 'type' => 'sms', 'provider' => 'test']], + ['amount' => 1, 'attributes' => ['result' => 'failure', 'type' => 'sms', 'provider' => 'test']], + ], $telemetry->records); + } + + public function testGeosmsPropagatesTelemetryToLocalAdapters(): void + { + $telemetry = new RecordingTelemetry(); + $default = new TelemetryAdapter([ + 'deliveredTo' => 0, + 'type' => 'sms', + 'results' => [], + ]); + $local = new TelemetryAdapter([ + 'deliveredTo' => 1, + 'type' => 'sms', + 'results' => [ + ['recipient' => '+911234567890', 'status' => 'success', 'error' => ''], + ], + ]); + + $adapter = new GEOSMS($default); + $adapter->setTelemetry($telemetry); + $adapter->setLocal(CallingCode::INDIA, $local); + $adapter->send((new SMS(['+911234567890'], 'Hello'))->setOrigin('internal')); + + $this->assertSame([ + ['amount' => 1, 'attributes' => ['result' => 'success', 'origin' => 'internal', 'type' => 'sms', 'provider' => 'test']], + ], $telemetry->records); + } + public function testDefaultTelemetryDoesNothing(): void { $adapter = new TelemetryAdapter([ @@ -69,7 +117,7 @@ public function testDefaultTelemetryDoesNothing(): void } } -class TelemetryAdapter extends Adapter +class TelemetryAdapter extends SMSAdapter { /** * @param array|null $response @@ -86,16 +134,6 @@ public function getName(): string return 'Test'; } - public function getType(): string - { - return 'sms'; - } - - public function getMessageType(): string - { - return SMS::class; - } - public function getMaxMessagesPerRequest(): int { return 100; @@ -104,7 +142,7 @@ public function getMaxMessagesPerRequest(): int /** * @return array */ - protected function process(Message $message): array + protected function process(SMS $message): array { if ($this->error !== null) { throw $this->error; From 0668001aa1e1cba47b16d6b84822f686dfa48b31 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 16 Jun 2026 09:07:17 +0530 Subject: [PATCH 3/3] Require origin metadata on messages --- src/Utopia/Messaging/Adapter.php | 2 +- src/Utopia/Messaging/Message.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index f8013112..62ae91c2 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -103,7 +103,7 @@ private function recordSend(Message $message, int $recipients, int $delivered): */ private function telemetryAttributes(Message $message, array $attributes = []): array { - if (\method_exists($message, 'getOrigin') && $message->getOrigin() !== null) { + if ($message->getOrigin() !== null) { $attributes['origin'] = $message->getOrigin(); } diff --git a/src/Utopia/Messaging/Message.php b/src/Utopia/Messaging/Message.php index 3b5e21ae..e7ddc9f9 100644 --- a/src/Utopia/Messaging/Message.php +++ b/src/Utopia/Messaging/Message.php @@ -7,4 +7,7 @@ */ interface Message { + public function setOrigin(?string $origin): self; + + public function getOrigin(): ?string; }