Skip to content

Commit ab1f0c7

Browse files
authored
Add Laravel Pennant feature flag integration (#1061)
1 parent 66e2ac0 commit ab1f0c7

File tree

5 files changed

+151
-7
lines changed

5 files changed

+151
-7
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ jobs:
154154
# friendsofphp/php-cs-fixer: No need for this package to run phpunit and it conflicts with older Laravel versions
155155
# livewire/livewire: Only supported on Laravel 7.0 and above
156156
# laravel/folio: Only supported on PHP 8.1 + Laravel 10.0 and above
157-
composer remove friendsofphp/php-cs-fixer livewire/livewire laravel/folio --dev --no-interaction --no-update
157+
# laravel/pennant: Only supported on PHP 8.1 + Laravel 10.0 and above
158+
composer remove friendsofphp/php-cs-fixer livewire/livewire laravel/folio laravel/pennant --dev --no-interaction --no-update
158159
159160
# Require the correct versions we want to run phpunit for
160161
composer require \

composer.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"require": {
2626
"php": "^7.2 | ^8.0",
2727
"illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0",
28-
"sentry/sentry": "^4.16.0",
28+
"sentry/sentry": "^4.18.0",
2929
"symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0",
3030
"nyholm/psr7": "^1.0"
3131
},
@@ -35,15 +35,16 @@
3535
}
3636
},
3737
"require-dev": {
38-
"phpunit/phpunit": "^8.4 | ^9.3 | ^10.4 | ^11.5",
38+
"friendsofphp/php-cs-fixer": "^3.11",
39+
"guzzlehttp/guzzle": "^7.2",
40+
"laravel/folio": "^1.1",
3941
"laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0",
42+
"laravel/pennant": "^1.0",
4043
"livewire/livewire": "^2.0 | ^3.0",
41-
"orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
42-
"friendsofphp/php-cs-fixer": "^3.11",
4344
"mockery/mockery": "^1.3",
45+
"orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
4446
"phpstan/phpstan": "^1.10",
45-
"laravel/folio": "^1.1",
46-
"guzzlehttp/guzzle": "^7.2"
47+
"phpunit/phpunit": "^8.4 | ^9.3 | ^10.4 | ^11.5"
4748
},
4849
"autoload-dev": {
4950
"psr-4": {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Features;
4+
5+
use Sentry\State\Scope;
6+
use Illuminate\Contracts\Events\Dispatcher;
7+
use Sentry\SentrySdk;
8+
use Laravel\Pennant\Feature as Pennant;
9+
use Laravel\Pennant\Events\FeatureResolved;
10+
use Laravel\Pennant\Events\FeatureRetrieved;
11+
12+
class PennantPackageIntegration extends Feature
13+
{
14+
private const FEATURE_KEY = 'pennant';
15+
16+
public function isApplicable(): bool
17+
{
18+
return class_exists(Pennant::class);
19+
}
20+
21+
public function onBoot(Dispatcher $events): void
22+
{
23+
$events->listen(FeatureRetrieved::class, [$this, 'handleFeatureRetrieved']);
24+
}
25+
26+
public function handleFeatureRetrieved($feature): void
27+
{
28+
SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($feature) {
29+
// The value of the feature is not always a bool (Rich Feature Values) but only bools are supported.
30+
// The feature is considered "active" if its value is not explicitly false following Pennant's logic.
31+
$scope->addFeatureFlag($feature->feature, $feature->value !== false);
32+
});
33+
}
34+
}

src/Sentry/Laravel/ServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class ServiceProvider extends BaseServiceProvider
7474
Features\HttpClientIntegration::class,
7575
Features\FolioPackageIntegration::class,
7676
Features\NotificationsIntegration::class,
77+
Features\PennantPackageIntegration::class,
7778
Features\LivewirePackageIntegration::class,
7879
Features\ConsoleSchedulingIntegration::class,
7980
];
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Tests\Features;
4+
5+
use Laravel\Folio\Folio;
6+
use Laravel\Pennant\Feature;
7+
use Sentry\Event;
8+
use Sentry\EventType;
9+
use Sentry\Laravel\Integration;
10+
use Illuminate\Config\Repository;
11+
use Sentry\Laravel\Tests\TestCase;
12+
use Illuminate\Database\Eloquent\Model;
13+
14+
class PennantPackageIntegrationTest extends TestCase
15+
{
16+
protected function setUp(): void
17+
{
18+
if (!class_exists(Feature::class)) {
19+
$this->markTestSkipped('Laravel Pennant package is not installed.');
20+
}
21+
22+
parent::setUp();
23+
}
24+
25+
protected function defineEnvironment($app): void
26+
{
27+
parent::defineEnvironment($app);
28+
29+
tap($app['config'], static function (Repository $config) {
30+
// Force Pennant to use the array driver instead of the database which is the default
31+
$config->set('pennant.default', 'array');
32+
$config->set('pennant.stores.array', ['driver' => 'array']);
33+
});
34+
}
35+
36+
public function testPennantFeatureIsRecorded(): void
37+
{
38+
Feature::define('new-dashboard', static function () {
39+
return true;
40+
});
41+
42+
Feature::active('new-dashboard');
43+
44+
$scope = $this->getCurrentSentryScope();
45+
46+
$event = $scope->applyToEvent(Event::createEvent());
47+
48+
$this->assertArrayHasKey('flags', $event->getContexts());
49+
50+
$flags = $event->getContexts()['flags']['values'];
51+
52+
$this->assertEquals([
53+
[
54+
'flag' => 'new-dashboard',
55+
'result' => true,
56+
]
57+
], $flags);
58+
}
59+
60+
public function testPennantRichFeatureIsRecordedAsActive(): void
61+
{
62+
Feature::define('dashboard-version', static function () {
63+
return 'dark';
64+
});
65+
66+
Feature::value('dashboard-version');
67+
68+
$scope = $this->getCurrentSentryScope();
69+
70+
$event = $scope->applyToEvent(Event::createEvent());
71+
72+
$this->assertArrayHasKey('flags', $event->getContexts());
73+
74+
$flags = $event->getContexts()['flags']['values'];
75+
76+
$this->assertEquals([
77+
[
78+
'flag' => 'dashboard-version',
79+
'result' => true,
80+
]
81+
], $flags);
82+
}
83+
84+
public function testPennantRichFeatureIsRecordedAsInactive(): void
85+
{
86+
Feature::define('dashboard-version', static function () {
87+
return false;
88+
});
89+
90+
Feature::value('dashboard-version');
91+
92+
$scope = $this->getCurrentSentryScope();
93+
94+
$event = $scope->applyToEvent(Event::createEvent());
95+
96+
$this->assertArrayHasKey('flags', $event->getContexts());
97+
98+
$flags = $event->getContexts()['flags']['values'];
99+
100+
$this->assertEquals([
101+
[
102+
'flag' => 'dashboard-version',
103+
'result' => false,
104+
]
105+
], $flags);
106+
}
107+
}

0 commit comments

Comments
 (0)