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/Drivers/DatabaseDriver.php b/src/Drivers/DatabaseDriver.php index c18c669..b4609b1 100644 --- a/src/Drivers/DatabaseDriver.php +++ b/src/Drivers/DatabaseDriver.php @@ -6,7 +6,7 @@ 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; @@ -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/FeatureManager.php b/src/FeatureManager.php index b1c4781..ee6babc 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\Driver; 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.