From 2e892f17f200b153bf529909f121186eae74c2bf Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 5 Nov 2025 15:45:24 +0100 Subject: [PATCH 1/2] Add Laravel Pennant feature flag integration --- .github/workflows/ci.yaml | 3 +- composer.json | 13 ++++--- .../Features/PennantPackageIntegration.php | 38 +++++++++++++++++++ src/Sentry/Laravel/ServiceProvider.php | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 src/Sentry/Laravel/Features/PennantPackageIntegration.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e5de6e6d..2ec5dbc3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -154,7 +154,8 @@ jobs: # friendsofphp/php-cs-fixer: No need for this package to run phpunit and it conflicts with older Laravel versions # livewire/livewire: Only supported on Laravel 7.0 and above # laravel/folio: Only supported on PHP 8.1 + Laravel 10.0 and above - composer remove friendsofphp/php-cs-fixer livewire/livewire laravel/folio --dev --no-interaction --no-update + # laravel/pennant: Only supported on PHP 8.1 + Laravel 10.0 and above + composer remove friendsofphp/php-cs-fixer livewire/livewire laravel/folio laravel/pennant --dev --no-interaction --no-update # Require the correct versions we want to run phpunit for composer require \ diff --git a/composer.json b/composer.json index c5a2cf98..592bbd4c 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require": { "php": "^7.2 | ^8.0", "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", - "sentry/sentry": "^4.16.0", + "sentry/sentry": "^4.18.0", "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0", "nyholm/psr7": "^1.0" }, @@ -35,15 +35,16 @@ } }, "require-dev": { - "phpunit/phpunit": "^8.4 | ^9.3 | ^10.4 | ^11.5", + "friendsofphp/php-cs-fixer": "^3.11", + "guzzlehttp/guzzle": "^7.2", + "laravel/folio": "^1.1", "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", + "laravel/pennant": "^1.0", "livewire/livewire": "^2.0 | ^3.0", - "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", - "friendsofphp/php-cs-fixer": "^3.11", "mockery/mockery": "^1.3", + "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", "phpstan/phpstan": "^1.10", - "laravel/folio": "^1.1", - "guzzlehttp/guzzle": "^7.2" + "phpunit/phpunit": "^8.4 | ^9.3 | ^10.4 | ^11.5" }, "autoload-dev": { "psr-4": { diff --git a/src/Sentry/Laravel/Features/PennantPackageIntegration.php b/src/Sentry/Laravel/Features/PennantPackageIntegration.php new file mode 100644 index 00000000..2daaa849 --- /dev/null +++ b/src/Sentry/Laravel/Features/PennantPackageIntegration.php @@ -0,0 +1,38 @@ +listen(FeatureRetrieved::class, [$this, 'handleFeatureRetrieved']); + } + + public function handleFeatureRetrieved($feature): void + { + if (!is_bool($feature->value)) { + return; // rich features are not supported yet + } + + SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($feature) { + // The value of the feature is not always a bool (Rich Feature Values) but only bools are supported. + // The feature is considered "active" if its value is not explicitly false following Pennant's logic. + $scope->addFeatureFlag($feature->feature, $feature->value !== false); + }); + } +} diff --git a/src/Sentry/Laravel/ServiceProvider.php b/src/Sentry/Laravel/ServiceProvider.php index f50b1a6c..22a8f6da 100644 --- a/src/Sentry/Laravel/ServiceProvider.php +++ b/src/Sentry/Laravel/ServiceProvider.php @@ -74,6 +74,7 @@ class ServiceProvider extends BaseServiceProvider Features\HttpClientIntegration::class, Features\FolioPackageIntegration::class, Features\NotificationsIntegration::class, + Features\PennantPackageIntegration::class, Features\LivewirePackageIntegration::class, Features\ConsoleSchedulingIntegration::class, ]; From 5a50a95b1f5378459c85c92c04c2c5ae39796ba3 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 5 Nov 2025 15:54:27 +0100 Subject: [PATCH 2/2] Add tests --- .../Features/PennantPackageIntegration.php | 4 - .../PennantPackageIntegrationTest.php | 107 ++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 test/Sentry/Features/PennantPackageIntegrationTest.php diff --git a/src/Sentry/Laravel/Features/PennantPackageIntegration.php b/src/Sentry/Laravel/Features/PennantPackageIntegration.php index 2daaa849..d7d42caf 100644 --- a/src/Sentry/Laravel/Features/PennantPackageIntegration.php +++ b/src/Sentry/Laravel/Features/PennantPackageIntegration.php @@ -25,10 +25,6 @@ public function onBoot(Dispatcher $events): void public function handleFeatureRetrieved($feature): void { - if (!is_bool($feature->value)) { - return; // rich features are not supported yet - } - SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($feature) { // The value of the feature is not always a bool (Rich Feature Values) but only bools are supported. // The feature is considered "active" if its value is not explicitly false following Pennant's logic. diff --git a/test/Sentry/Features/PennantPackageIntegrationTest.php b/test/Sentry/Features/PennantPackageIntegrationTest.php new file mode 100644 index 00000000..44749f3a --- /dev/null +++ b/test/Sentry/Features/PennantPackageIntegrationTest.php @@ -0,0 +1,107 @@ +markTestSkipped('Laravel Pennant package is not installed.'); + } + + parent::setUp(); + } + + protected function defineEnvironment($app): void + { + parent::defineEnvironment($app); + + tap($app['config'], static function (Repository $config) { + // Force Pennant to use the array driver instead of the database which is the default + $config->set('pennant.default', 'array'); + $config->set('pennant.stores.array', ['driver' => 'array']); + }); + } + + public function testPennantFeatureIsRecorded(): void + { + Feature::define('new-dashboard', static function () { + return true; + }); + + Feature::active('new-dashboard'); + + $scope = $this->getCurrentSentryScope(); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertArrayHasKey('flags', $event->getContexts()); + + $flags = $event->getContexts()['flags']['values']; + + $this->assertEquals([ + [ + 'flag' => 'new-dashboard', + 'result' => true, + ] + ], $flags); + } + + public function testPennantRichFeatureIsRecordedAsActive(): void + { + Feature::define('dashboard-version', static function () { + return 'dark'; + }); + + Feature::value('dashboard-version'); + + $scope = $this->getCurrentSentryScope(); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertArrayHasKey('flags', $event->getContexts()); + + $flags = $event->getContexts()['flags']['values']; + + $this->assertEquals([ + [ + 'flag' => 'dashboard-version', + 'result' => true, + ] + ], $flags); + } + + public function testPennantRichFeatureIsRecordedAsInactive(): void + { + Feature::define('dashboard-version', static function () { + return false; + }); + + Feature::value('dashboard-version'); + + $scope = $this->getCurrentSentryScope(); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertArrayHasKey('flags', $event->getContexts()); + + $flags = $event->getContexts()['flags']['values']; + + $this->assertEquals([ + [ + 'flag' => 'dashboard-version', + 'result' => false, + ] + ], $flags); + } +}