vendor/shopware/core/Framework/Feature.php line 175

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework;
  3. use PHPUnit\Framework\TestCase;
  4. use Shopware\Core\DevOps\Environment\EnvironmentHelper;
  5. use Shopware\Core\Framework\Log\Package;
  6. use Shopware\Core\Framework\Script\Debugging\ScriptTraces;
  7. /**
  8.  * @phpstan-type FeatureFlagConfig array{name?: string, default?: boolean, major?: boolean, description?: string}
  9.  */
  10. #[Package('core')]
  11. class Feature
  12. {
  13.     final public const ALL_MAJOR 'major';
  14.     /**
  15.      * @var array<bool>
  16.      */
  17.     private static array $silent = [];
  18.     /**
  19.      * @var array<string, FeatureFlagConfig>
  20.      */
  21.     private static array $registeredFeatures = [];
  22.     public static function normalizeName(string $name): string
  23.     {
  24.         /*
  25.          * Examples:
  26.          * - NEXT-1234
  27.          * - FEATURE_NEXT_1234
  28.          * - SAAS_321
  29.          * - v6.5.0.0 => v6_5_0_0
  30.          */
  31.         return \strtoupper(\str_replace(['.'':''-'], '_'$name));
  32.     }
  33.     /**
  34.      * @param array<string> $features
  35.      *
  36.      * @return mixed|null
  37.      */
  38.     public static function fake(array $features\Closure $closure)
  39.     {
  40.         $before self::$registeredFeatures;
  41.         $serverVarsBackup $_SERVER;
  42.         $result null;
  43.         try {
  44.             self::$registeredFeatures = [];
  45.             foreach ($_SERVER as $key => $value) {
  46.                 if (str_starts_with($key'v6.') || str_starts_with($key'FEATURE_') || str_starts_with($key'V6_')) {
  47.                     // set to false so that $_ENV is not checked
  48.                     $_SERVER[$key] = false;
  49.                 }
  50.             }
  51.             if ($features) {
  52.                 foreach ($features as $feature) {
  53.                     $_SERVER[Feature::normalizeName($feature)] = true;
  54.                 }
  55.             }
  56.             $result $closure();
  57.         } finally {
  58.             self::$registeredFeatures $before;
  59.             $_SERVER $serverVarsBackup;
  60.         }
  61.         return $result;
  62.     }
  63.     public static function isActive(string $feature): bool
  64.     {
  65.         $env EnvironmentHelper::getVariable('APP_ENV''prod');
  66.         $feature self::normalizeName($feature);
  67.         if (self::$registeredFeatures !== []
  68.             && !isset(self::$registeredFeatures[$feature])
  69.             && $env !== 'prod'
  70.         ) {
  71.             trigger_error('Unknown feature "' $feature '"'\E_USER_WARNING);
  72.         }
  73.         $featureAll EnvironmentHelper::getVariable('FEATURE_ALL''');
  74.         if (self::isTrue((string) $featureAll) && (self::$registeredFeatures === [] || \array_key_exists($featureself::$registeredFeatures))) {
  75.             if ($featureAll === Feature::ALL_MAJOR) {
  76.                 return true;
  77.             }
  78.             // return true if it's registered and not a major feature
  79.             if (isset(self::$registeredFeatures[$feature]) && (self::$registeredFeatures[$feature]['major'] ?? false) === false) {
  80.                 return true;
  81.             }
  82.         }
  83.         if (!EnvironmentHelper::hasVariable($feature) && !EnvironmentHelper::hasVariable(\strtolower($feature))) {
  84.             $fallback self::$registeredFeatures[$feature]['default'] ?? false;
  85.             return (bool) $fallback;
  86.         }
  87.         return self::isTrue(trim((string) EnvironmentHelper::getVariable($feature)));
  88.     }
  89.     public static function ifActive(string $flagName\Closure $closure): void
  90.     {
  91.         self::isActive($flagName) && $closure();
  92.     }
  93.     public static function callSilentIfInactive(string $flagName\Closure $closure): void
  94.     {
  95.         $before = isset(self::$silent[$flagName]);
  96.         self::$silent[$flagName] = true;
  97.         try {
  98.             if (!self::isActive($flagName)) {
  99.                 $closure();
  100.             }
  101.         } finally {
  102.             if (!$before) {
  103.                 unset(self::$silent[$flagName]);
  104.             }
  105.         }
  106.     }
  107.     public static function silent(string $flagName\Closure $closure): mixed
  108.     {
  109.         $before = isset(self::$silent[$flagName]);
  110.         self::$silent[$flagName] = true;
  111.         try {
  112.             $result $closure();
  113.         } finally {
  114.             if (!$before) {
  115.                 unset(self::$silent[$flagName]);
  116.             }
  117.         }
  118.         return $result;
  119.     }
  120.     public static function skipTestIfInActive(string $flagNameTestCase $test): void
  121.     {
  122.         if (self::isActive($flagName)) {
  123.             return;
  124.         }
  125.         $test->markTestSkipped('Skipping feature test for flag  "' $flagName '"');
  126.     }
  127.     public static function skipTestIfActive(string $flagNameTestCase $test): void
  128.     {
  129.         if (!self::isActive($flagName)) {
  130.             return;
  131.         }
  132.         $test->markTestSkipped('Skipping feature test for flag  "' $flagName '"');
  133.     }
  134.     public static function throwException(string $flagstring $messagebool $state true): void
  135.     {
  136.         if (self::isActive($flag) === $state || (self::$registeredFeatures !== [] && !self::has($flag))) {
  137.             throw new \RuntimeException($message);
  138.         }
  139.         if (\PHP_SAPI !== 'cli') {
  140.             ScriptTraces::addDeprecationNotice($message);
  141.         }
  142.     }
  143.     public static function triggerDeprecationOrThrow(string $majorFlagstring $message): void
  144.     {
  145.         if (self::isActive($majorFlag) || (self::$registeredFeatures !== [] && !self::has($majorFlag))) {
  146.             throw new \RuntimeException('Tried to access deprecated functionality: ' $message);
  147.         }
  148.         if (!isset(self::$silent[$majorFlag]) || !self::$silent[$majorFlag]) {
  149.             if (\PHP_SAPI !== 'cli') {
  150.                 ScriptTraces::addDeprecationNotice($message);
  151.             }
  152.             trigger_deprecation('shopware/core'''$message);
  153.         }
  154.     }
  155.     public static function deprecatedMethodMessage(string $classstring $methodstring $majorVersion, ?string $replacement null): string
  156.     {
  157.         $message \sprintf(
  158.             'Method "%s::%s()" is deprecated and will be removed in %s.',
  159.             $class,
  160.             $method,
  161.             $majorVersion
  162.         );
  163.         if ($replacement) {
  164.             $message \sprintf('%s Use "%s" instead.'$message$replacement);
  165.         }
  166.         return $message;
  167.     }
  168.     public static function deprecatedClassMessage(string $classstring $majorVersion, ?string $replacement null): string
  169.     {
  170.         $message \sprintf(
  171.             'Class "%s" is deprecated and will be removed in %s.',
  172.             $class,
  173.             $majorVersion
  174.         );
  175.         if ($replacement) {
  176.             $message \sprintf('%s Use "%s" instead.'$message$replacement);
  177.         }
  178.         return $message;
  179.     }
  180.     public static function has(string $flag): bool
  181.     {
  182.         $flag self::normalizeName($flag);
  183.         return isset(self::$registeredFeatures[$flag]);
  184.     }
  185.     /**
  186.      * @return array<string, bool>
  187.      */
  188.     public static function getAll(bool $denormalized true): array
  189.     {
  190.         $resolvedFlags = [];
  191.         foreach (self::$registeredFeatures as $name => $_) {
  192.             $active self::isActive($name);
  193.             $resolvedFlags[$name] = $active;
  194.             if (!$denormalized) {
  195.                 continue;
  196.             }
  197.             $resolvedFlags[self::denormalize($name)] = $active;
  198.         }
  199.         return $resolvedFlags;
  200.     }
  201.     /**
  202.      * @param FeatureFlagConfig $metaData
  203.      *
  204.      * @internal
  205.      */
  206.     public static function registerFeature(string $name, array $metaData = []): void
  207.     {
  208.         $name self::normalizeName($name);
  209.         // merge with existing data
  210.         /** @var array{name?: string, default?: boolean, major?: boolean, description?: string} $metaData */
  211.         $metaData array_merge(
  212.             self::$registeredFeatures[$name] ?? [],
  213.             $metaData
  214.         );
  215.         // set defaults
  216.         $metaData['major'] = (bool) ($metaData['major'] ?? false);
  217.         $metaData['default'] = (bool) ($metaData['default'] ?? false);
  218.         $metaData['description'] = (string) ($metaData['description'] ?? '');
  219.         self::$registeredFeatures[$name] = $metaData;
  220.     }
  221.     /**
  222.      * @param array<string, FeatureFlagConfig>|string[] $registeredFeatures
  223.      *
  224.      * @internal
  225.      */
  226.     public static function registerFeatures(iterable $registeredFeatures): void
  227.     {
  228.         foreach ($registeredFeatures as $flag => $data) {
  229.             // old format
  230.             if (\is_string($data)) {
  231.                 $flag $data;
  232.                 $data = [];
  233.             }
  234.             self::registerFeature($flag$data);
  235.         }
  236.     }
  237.     /**
  238.      * @internal
  239.      */
  240.     public static function resetRegisteredFeatures(): void
  241.     {
  242.         self::$registeredFeatures = [];
  243.     }
  244.     /**
  245.      * @internal
  246.      *
  247.      * @return array<string, FeatureFlagConfig>
  248.      */
  249.     public static function getRegisteredFeatures(): array
  250.     {
  251.         return self::$registeredFeatures;
  252.     }
  253.     private static function isTrue(string $value): bool
  254.     {
  255.         return $value
  256.             && $value !== 'false'
  257.             && $value !== '0'
  258.             && $value !== '';
  259.     }
  260.     private static function denormalize(string $name): string
  261.     {
  262.         return \strtolower(\str_replace(['_'], '.'$name));
  263.     }
  264. }