From d37343de1fb38926c96e8c491e3567b49029cd34 Mon Sep 17 00:00:00 2001 From: Rushabh Date: Thu, 12 Feb 2026 11:46:18 +0530 Subject: [PATCH 1/2] Refactor: Ensure Laravel 9 compatibility and resolve IDE conflicts This commit: - Renames Driver interface to DriverContract to avoid workspace collisions. - Handles UniqueConstraintViolationException via helper for L9/L10 compatibility. - Updates composer.json for L9 dependencies and package rename (qrstuff/pennant). - Refactors TestCase.php for Testbench 7 support. --- composer.json | 28 ++++----- .../{Driver.php => DriverContract.php} | 2 +- src/Drivers/ArrayDriver.php | 4 +- src/Drivers/DatabaseDriver.php | 58 +++++++++++++++---- src/Drivers/Decorator.php | 8 +-- src/Feature.php | 2 +- src/FeatureManager.php | 1 + tests/TestCase.php | 32 +++++++++- 8 files changed, 99 insertions(+), 36 deletions(-) rename src/Contracts/{Driver.php => DriverContract.php} (98%) diff --git a/composer.json b/composer.json index 1edb55e..6569ae9 100644 --- a/composer.json +++ b/composer.json @@ -1,33 +1,33 @@ { - "name": "laravel/pennant", + "name": "qrstuff/pennant", "description": "A simple, lightweight library for managing feature flags.", "keywords": ["laravel", "pennant", "feature", "flags"], - "homepage": "https://github.com/laravel/pennant", + "homepage": "https://bitbucket.org/qrcg/pennant", "license": "MIT", - "support": { - "issues": "https://github.com/laravel/pennant/issues", - "source": "https://github.com/laravel/pennant" - }, "authors": [ + { + "name": "QRStuff Team", + "email": "engineering@qrstuff.com" + }, { "name": "Taylor Otwell", "email": "taylor@laravel.com" } ], "require": { - "php": "^8.1", - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/container": "^10.0|^11.0|^12.0", - "illuminate/contracts": "^10.0|^11.0|^12.0", - "illuminate/database": "^10.0|^11.0|^12.0", - "illuminate/queue": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.0", + "illuminate/console": "^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^9.0|^10.0|^11.0|^12.0", + "illuminate/queue": "^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^9.0|^10.0|^11.0|^12.0", "symfony/console": "^6.0|^7.0", "symfony/finder": "^6.0|^7.0" }, "require-dev": { "laravel/octane": "^1.4|^2.0", - "orchestra/testbench": "^8.36|^9.15|^10.8", + "orchestra/testbench": "^7.0|^8.36|^9.15|^10.8", "phpstan/phpstan": "^1.10" }, "autoload": { diff --git a/src/Contracts/Driver.php b/src/Contracts/DriverContract.php similarity index 98% rename from src/Contracts/Driver.php rename to src/Contracts/DriverContract.php index f0592c2..7bafdc3 100644 --- a/src/Contracts/Driver.php +++ b/src/Contracts/DriverContract.php @@ -2,7 +2,7 @@ namespace Laravel\Pennant\Contracts; -interface Driver +interface DriverContract { /** * Define an initial feature flag state resolver. diff --git a/src/Drivers/ArrayDriver.php b/src/Drivers/ArrayDriver.php index 1ed369b..9562cc2 100644 --- a/src/Drivers/ArrayDriver.php +++ b/src/Drivers/ArrayDriver.php @@ -5,13 +5,13 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\Collection; use Laravel\Pennant\Contracts\CanListStoredFeatures; -use Laravel\Pennant\Contracts\Driver; +use Laravel\Pennant\Contracts\DriverContract; use Laravel\Pennant\Contracts\HasFlushableCache; use Laravel\Pennant\Events\UnknownFeatureResolved; use Laravel\Pennant\Feature; use stdClass; -class ArrayDriver implements CanListStoredFeatures, Driver, HasFlushableCache +class ArrayDriver implements CanListStoredFeatures, DriverContract, HasFlushableCache { /** * The event dispatcher. diff --git a/src/Drivers/DatabaseDriver.php b/src/Drivers/DatabaseDriver.php index c18c669..0a500e9 100644 --- a/src/Drivers/DatabaseDriver.php +++ b/src/Drivers/DatabaseDriver.php @@ -6,18 +6,18 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Connection; use Illuminate\Database\DatabaseManager; -use Illuminate\Database\UniqueConstraintViolationException; +use Illuminate\Database\QueryException; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Laravel\Pennant\Contracts\CanListStoredFeatures; use Laravel\Pennant\Contracts\CanSetManyFeaturesForScopes; -use Laravel\Pennant\Contracts\Driver; +use Laravel\Pennant\Contracts\DriverContract; use Laravel\Pennant\Events\UnknownFeatureResolved; use Laravel\Pennant\Feature; use RuntimeException; use stdClass; -class DatabaseDriver implements CanListStoredFeatures, CanSetManyFeaturesForScopes, Driver +class DatabaseDriver implements CanListStoredFeatures, CanSetManyFeaturesForScopes, DriverContract { /** * The database connection. @@ -101,11 +101,8 @@ public function __construct(DatabaseManager $db, Dispatcher $events, Repository /** * Define an initial feature flag state resolver. - * - * @param string $feature - * @param (callable(mixed $scope): mixed) $resolver */ - public function define($feature, $resolver): void + public function define(string $feature, callable $resolver): void { $this->featureStateResolvers[$feature] = $resolver; } @@ -180,7 +177,11 @@ public function getAll($features): array if ($inserts->isNotEmpty()) { // @phpstan-ignore method.impossibleType try { $this->insertMany($inserts->all()); - } catch (UniqueConstraintViolationException $e) { + } catch (\Exception $e) { + if (! $this->isUniqueConstraintViolation($e)) { + throw $e; + } + if ($this->retryDepth === 2) { throw new RuntimeException('Unable to insert feature values into the database.', previous: $e); } @@ -215,7 +216,11 @@ public function get($feature, $scope): mixed try { $this->insert($feature, $scope, $value); - } catch (UniqueConstraintViolationException $e) { + } catch (\Exception $e) { + if (! $this->isUniqueConstraintViolation($e)) { + throw $e; + } + if ($this->retryDepth === 1) { throw new RuntimeException('Unable to insert feature value into the database.', previous: $e); } @@ -386,11 +391,11 @@ public function delete($feature, $scope): void } /** - * Purge the given feature from storage. + * Purge the given features from storage. * - * @param array|null $features + * @param array|null $features */ - public function purge($features): void + public function purge(?array $features): void { if ($features === null) { $this->newQuery()->delete(); @@ -424,4 +429,33 @@ protected function connection() $this->config->get("pennant.stores.{$this->name}.connection") ?? null ); } + + /** + * Determine if the given exception is a unique constraint violation. + * + * @param \Exception $e + * @return bool + */ + protected function isUniqueConstraintViolation($e) + { + if (class_exists('Illuminate\Database\UniqueConstraintViolationException') && + $e instanceof \Illuminate\Database\UniqueConstraintViolationException) { + return true; + } + + if (! $e instanceof QueryException) { + return false; + } + + $connection = $this->connection(); + $driverName = $connection->getDriverName(); + + return match ($driverName) { + 'sqlite' => str_contains($e->getMessage(), 'UNIQUE constraint failed'), + 'mysql' => $e->getCode() === '23000' && str_contains($e->getMessage(), '1062 Duplicate entry'), + 'pgsql' => $e->getCode() === '23505', + 'sqlsrv' => $e->getCode() === '2601' || $e->getCode() === '2627', + default => false, + }; + } } diff --git a/src/Drivers/Decorator.php b/src/Drivers/Decorator.php index 6a46214..11be758 100644 --- a/src/Drivers/Decorator.php +++ b/src/Drivers/Decorator.php @@ -13,7 +13,7 @@ use Laravel\Pennant\Contracts\CanListStoredFeatures; use Laravel\Pennant\Contracts\CanSetManyFeaturesForScopes; use Laravel\Pennant\Contracts\DefinesFeaturesExternally; -use Laravel\Pennant\Contracts\Driver; +use Laravel\Pennant\Contracts\DriverContract; use Laravel\Pennant\Contracts\FeatureScopeable; use Laravel\Pennant\Contracts\HasFlushableCache; use Laravel\Pennant\Events\AllFeaturesPurged; @@ -39,7 +39,7 @@ /** * @mixin \Laravel\Pennant\PendingScopedFeatureInteraction */ -class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, Driver, HasFlushableCache +class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, DriverContract, HasFlushableCache { use Macroable { __call as macroCall; @@ -55,7 +55,7 @@ class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, D /** * The driver being decorated. * - * @var \Laravel\Pennant\Contracts\Driver + * @var \Laravel\Pennant\Contracts\DriverContract */ protected $driver; @@ -91,7 +91,7 @@ class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, D * Create a new driver decorator instance. * * @param string $name - * @param \Laravel\Pennant\Contracts\Driver $driver + * @param \Laravel\Pennant\Contracts\DriverContract $driver * @param (callable(): mixed) $defaultScopeResolver * @param \Illuminate\Contracts\Container\Container $container * @param \Illuminate\Support\Collection $cache diff --git a/src/Feature.php b/src/Feature.php index 5528a6e..0058c5c 100644 --- a/src/Feature.php +++ b/src/Feature.php @@ -30,7 +30,7 @@ * @method static string name(string $feature) * @method static array nameMap() * @method static mixed instance(string $name) - * @method static \Laravel\Pennant\Contracts\Driver getDriver() + * @method static \Laravel\Pennant\Contracts\DriverContract getDriver() * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) diff --git a/src/FeatureManager.php b/src/FeatureManager.php index b1c4781..3deb418 100644 --- a/src/FeatureManager.php +++ b/src/FeatureManager.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use InvalidArgumentException; +use Laravel\Pennant\Contracts\DriverContract; use Laravel\Pennant\Contracts\FeatureScopeSerializeable; use Laravel\Pennant\Drivers\ArrayDriver; use Laravel\Pennant\Drivers\DatabaseDriver; diff --git a/tests/TestCase.php b/tests/TestCase.php index a9c825f..f7d315d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,12 +3,40 @@ namespace Tests; use Laravel\Pennant\FeatureManager; -use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase as OrchestraTestCase; abstract class TestCase extends OrchestraTestCase { - use WithWorkbench; + /** + * Get package providers. + * + * @param \Illuminate\Foundation\Application $app + * @return array + */ + protected function getPackageProviders($app) + { + return [ + \Laravel\Pennant\PennantServiceProvider::class, + ]; + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function getEnvironmentSetUp($app) + { + $app['config']->set('database.default', 'testing'); + + if (file_exists(__DIR__.'/../workbench/database/migrations')) { + $app['config']->set('database.migrations', [ + realpath(__DIR__.'/../database/migrations'), + realpath(__DIR__.'/../workbench/database/migrations'), + ]); + } + } /** * Create an instance of the manager. From 958eb69fe61b25750afd372f48c19c3505ce82c9 Mon Sep 17 00:00:00 2001 From: Rushabh Date: Thu, 12 Feb 2026 12:32:38 +0530 Subject: [PATCH 2/2] Revert: Rename DriverContract back to Driver --- src/Contracts/{DriverContract.php => Driver.php} | 2 +- src/Drivers/ArrayDriver.php | 4 ++-- src/Drivers/DatabaseDriver.php | 4 ++-- src/Drivers/Decorator.php | 8 ++++---- src/Feature.php | 2 +- src/FeatureManager.php | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename src/Contracts/{DriverContract.php => Driver.php} (98%) diff --git a/src/Contracts/DriverContract.php b/src/Contracts/Driver.php similarity index 98% rename from src/Contracts/DriverContract.php rename to src/Contracts/Driver.php index 7bafdc3..f0592c2 100644 --- a/src/Contracts/DriverContract.php +++ b/src/Contracts/Driver.php @@ -2,7 +2,7 @@ namespace Laravel\Pennant\Contracts; -interface DriverContract +interface Driver { /** * Define an initial feature flag state resolver. diff --git a/src/Drivers/ArrayDriver.php b/src/Drivers/ArrayDriver.php index 9562cc2..1ed369b 100644 --- a/src/Drivers/ArrayDriver.php +++ b/src/Drivers/ArrayDriver.php @@ -5,13 +5,13 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\Collection; use Laravel\Pennant\Contracts\CanListStoredFeatures; -use Laravel\Pennant\Contracts\DriverContract; +use Laravel\Pennant\Contracts\Driver; use Laravel\Pennant\Contracts\HasFlushableCache; use Laravel\Pennant\Events\UnknownFeatureResolved; use Laravel\Pennant\Feature; use stdClass; -class ArrayDriver implements CanListStoredFeatures, DriverContract, HasFlushableCache +class ArrayDriver implements CanListStoredFeatures, Driver, HasFlushableCache { /** * The event dispatcher. diff --git a/src/Drivers/DatabaseDriver.php b/src/Drivers/DatabaseDriver.php index 0a500e9..b4609b1 100644 --- a/src/Drivers/DatabaseDriver.php +++ b/src/Drivers/DatabaseDriver.php @@ -11,13 +11,13 @@ use Illuminate\Support\Collection; use Laravel\Pennant\Contracts\CanListStoredFeatures; use Laravel\Pennant\Contracts\CanSetManyFeaturesForScopes; -use Laravel\Pennant\Contracts\DriverContract; +use Laravel\Pennant\Contracts\Driver; use Laravel\Pennant\Events\UnknownFeatureResolved; use Laravel\Pennant\Feature; use RuntimeException; use stdClass; -class DatabaseDriver implements CanListStoredFeatures, CanSetManyFeaturesForScopes, DriverContract +class DatabaseDriver implements CanListStoredFeatures, CanSetManyFeaturesForScopes, Driver { /** * The database connection. diff --git a/src/Drivers/Decorator.php b/src/Drivers/Decorator.php index 11be758..6a46214 100644 --- a/src/Drivers/Decorator.php +++ b/src/Drivers/Decorator.php @@ -13,7 +13,7 @@ use Laravel\Pennant\Contracts\CanListStoredFeatures; use Laravel\Pennant\Contracts\CanSetManyFeaturesForScopes; use Laravel\Pennant\Contracts\DefinesFeaturesExternally; -use Laravel\Pennant\Contracts\DriverContract; +use Laravel\Pennant\Contracts\Driver; use Laravel\Pennant\Contracts\FeatureScopeable; use Laravel\Pennant\Contracts\HasFlushableCache; use Laravel\Pennant\Events\AllFeaturesPurged; @@ -39,7 +39,7 @@ /** * @mixin \Laravel\Pennant\PendingScopedFeatureInteraction */ -class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, DriverContract, HasFlushableCache +class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, Driver, HasFlushableCache { use Macroable { __call as macroCall; @@ -55,7 +55,7 @@ class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, D /** * The driver being decorated. * - * @var \Laravel\Pennant\Contracts\DriverContract + * @var \Laravel\Pennant\Contracts\Driver */ protected $driver; @@ -91,7 +91,7 @@ class Decorator implements CanListStoredFeatures, CanSetManyFeaturesForScopes, D * Create a new driver decorator instance. * * @param string $name - * @param \Laravel\Pennant\Contracts\DriverContract $driver + * @param \Laravel\Pennant\Contracts\Driver $driver * @param (callable(): mixed) $defaultScopeResolver * @param \Illuminate\Contracts\Container\Container $container * @param \Illuminate\Support\Collection $cache diff --git a/src/Feature.php b/src/Feature.php index 0058c5c..5528a6e 100644 --- a/src/Feature.php +++ b/src/Feature.php @@ -30,7 +30,7 @@ * @method static string name(string $feature) * @method static array nameMap() * @method static mixed instance(string $name) - * @method static \Laravel\Pennant\Contracts\DriverContract getDriver() + * @method static \Laravel\Pennant\Contracts\Driver getDriver() * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) diff --git a/src/FeatureManager.php b/src/FeatureManager.php index 3deb418..ee6babc 100644 --- a/src/FeatureManager.php +++ b/src/FeatureManager.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use InvalidArgumentException; -use Laravel\Pennant\Contracts\DriverContract; +use Laravel\Pennant\Contracts\Driver; use Laravel\Pennant\Contracts\FeatureScopeSerializeable; use Laravel\Pennant\Drivers\ArrayDriver; use Laravel\Pennant\Drivers\DatabaseDriver;