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

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