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

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