FestinHegre/vendor/symfony/phpunit-bridge/DeprecationErrorHandler/Configuration.php
2024-09-26 17:26:04 +02:00

389 lines
12 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
/**
* @internal
*/
class Configuration
{
/**
* @var int[]
*/
private $thresholds;
/**
* @var string
*/
private $regex;
/**
* @var bool
*/
private $enabled = true;
/**
* @var bool[]
*/
private $verboseOutput;
/**
* @var string[]
*/
private $ignoreDeprecationPatterns = [];
/**
* @var bool
*/
private $generateBaseline = false;
/**
* @var string
*/
private $baselineFile = '';
/**
* @var array
*/
private $baselineDeprecations = [];
/**
* @var string|null
*/
private $logFile;
/**
* @param int[] $thresholds A hash associating groups to thresholds
* @param string $regex Will be matched against messages, to decide whether to display a stack trace
* @param bool[] $verboseOutput Keyed by groups
* @param string $ignoreFile The path to the ignore deprecation patterns file
* @param bool $generateBaseline Whether to generate or update the baseline file
* @param string $baselineFile The path to the baseline file
* @param string|null $logFile The path to the log file
*/
private function __construct(array $thresholds = [], string $regex = '', array $verboseOutput = [], string $ignoreFile = '', bool $generateBaseline = false, string $baselineFile = '', ?string $logFile = null)
{
$groups = ['total', 'indirect', 'direct', 'self'];
foreach ($thresholds as $group => $threshold) {
if (!\in_array($group, $groups, true)) {
throw new \InvalidArgumentException(sprintf('Unrecognized threshold "%s", expected one of "%s".', $group, implode('", "', $groups)));
}
if (!is_numeric($threshold)) {
throw new \InvalidArgumentException(sprintf('Threshold for group "%s" has invalid value "%s".', $group, $threshold));
}
$this->thresholds[$group] = (int) $threshold;
}
if (isset($this->thresholds['direct'])) {
$this->thresholds += [
'self' => $this->thresholds['direct'],
];
}
if (isset($this->thresholds['indirect'])) {
$this->thresholds += [
'direct' => $this->thresholds['indirect'],
'self' => $this->thresholds['indirect'],
];
}
foreach ($groups as $group) {
if (!isset($this->thresholds[$group])) {
$this->thresholds[$group] = 999999;
}
}
$this->regex = $regex;
$this->verboseOutput = [
'unsilenced' => true,
'direct' => true,
'indirect' => true,
'self' => true,
'other' => true,
];
foreach ($verboseOutput as $group => $status) {
if (!isset($this->verboseOutput[$group])) {
throw new \InvalidArgumentException(sprintf('Unsupported verbosity group "%s", expected one of "%s".', $group, implode('", "', array_keys($this->verboseOutput))));
}
$this->verboseOutput[$group] = $status;
}
if ($ignoreFile) {
if (!is_file($ignoreFile)) {
throw new \InvalidArgumentException(sprintf('The ignoreFile "%s" does not exist.', $ignoreFile));
}
set_error_handler(static function ($t, $m) use ($ignoreFile, &$line) {
throw new \RuntimeException(sprintf('Invalid pattern found in "%s" on line "%d"', $ignoreFile, 1 + $line).substr($m, 12));
});
try {
foreach (file($ignoreFile) as $line => $pattern) {
if ('#' !== (trim($pattern)[0] ?? '#')) {
preg_match($pattern, '');
$this->ignoreDeprecationPatterns[] = $pattern;
}
}
} finally {
restore_error_handler();
}
}
if ($generateBaseline && !$baselineFile) {
throw new \InvalidArgumentException('You cannot use the "generateBaseline" configuration option without providing a "baselineFile" configuration option.');
}
$this->generateBaseline = $generateBaseline;
$this->baselineFile = $baselineFile;
if ($this->baselineFile && !$this->generateBaseline) {
if (is_file($this->baselineFile)) {
$map = json_decode(file_get_contents($this->baselineFile));
foreach ($map as $baseline_deprecation) {
$this->baselineDeprecations[$baseline_deprecation->location][$baseline_deprecation->message] = $baseline_deprecation->count;
}
} else {
throw new \InvalidArgumentException(sprintf('The baselineFile "%s" does not exist.', $this->baselineFile));
}
}
$this->logFile = $logFile;
}
public function isEnabled(): bool
{
return $this->enabled;
}
/**
* @param DeprecationGroup[] $deprecationGroups
*/
public function tolerates(array $deprecationGroups): bool
{
$grandTotal = 0;
foreach ($deprecationGroups as $name => $group) {
if ('legacy' !== $name) {
$grandTotal += $group->count();
}
}
if ($grandTotal > $this->thresholds['total']) {
return false;
}
foreach (['self', 'direct', 'indirect'] as $deprecationType) {
if ($deprecationGroups[$deprecationType]->count() > $this->thresholds[$deprecationType]) {
return false;
}
}
return true;
}
public function isIgnoredDeprecation(Deprecation $deprecation): bool
{
if (!$this->ignoreDeprecationPatterns) {
return false;
}
$result = @preg_filter($this->ignoreDeprecationPatterns, '$0', $deprecation->getMessage());
if (\PREG_NO_ERROR !== preg_last_error()) {
throw new \RuntimeException(preg_last_error_msg());
}
return (bool) $result;
}
/**
* @param array<string,DeprecationGroup> $deprecationGroups
*
* @return bool true if the threshold is not reached for the deprecation type nor for the total
*/
public function toleratesForGroup(string $groupName, array $deprecationGroups): bool
{
$grandTotal = 0;
foreach ($deprecationGroups as $type => $group) {
if ('legacy' !== $type) {
$grandTotal += $group->count();
}
}
if ($grandTotal > $this->thresholds['total']) {
return false;
}
if (\in_array($groupName, ['self', 'direct', 'indirect'], true) && $deprecationGroups[$groupName]->count() > $this->thresholds[$groupName]) {
return false;
}
return true;
}
public function isBaselineDeprecation(Deprecation $deprecation): bool
{
if ($deprecation->isLegacy()) {
return false;
}
if ($deprecation->originatesFromDebugClassLoader()) {
$location = $deprecation->triggeringClass();
} elseif ($deprecation->originatesFromAnObject()) {
$location = $deprecation->originatingClass().'::'.$deprecation->originatingMethod();
} else {
$location = 'procedural code';
}
$message = $deprecation->getMessage();
$result = isset($this->baselineDeprecations[$location][$message]) && $this->baselineDeprecations[$location][$message] > 0;
if ($this->generateBaseline) {
if ($result) {
++$this->baselineDeprecations[$location][$message];
} else {
$this->baselineDeprecations[$location][$message] = 1;
$result = true;
}
} elseif ($result) {
--$this->baselineDeprecations[$location][$message];
}
return $result;
}
public function isGeneratingBaseline(): bool
{
return $this->generateBaseline;
}
public function getBaselineFile(): string
{
return $this->baselineFile;
}
public function writeBaseline(): void
{
$map = [];
foreach ($this->baselineDeprecations as $location => $messages) {
foreach ($messages as $message => $count) {
$map[] = [
'location' => $location,
'message' => $message,
'count' => $count,
];
}
}
file_put_contents($this->baselineFile, json_encode($map, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
}
public function shouldDisplayStackTrace(string $message): bool
{
return '' !== $this->regex && preg_match($this->regex, $message);
}
public function isInRegexMode(): bool
{
return '' !== $this->regex;
}
public function verboseOutput($group): bool
{
return $this->verboseOutput[$group];
}
public function shouldWriteToLogFile(): bool
{
return null !== $this->logFile;
}
public function getLogFile(): ?string
{
return $this->logFile;
}
/**
* @param string $serializedConfiguration An encoded string, for instance max[total]=1234&max[indirect]=42
*/
public static function fromUrlEncodedString(string $serializedConfiguration): self
{
parse_str($serializedConfiguration, $normalizedConfiguration);
foreach (array_keys($normalizedConfiguration) as $key) {
if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'ignoreFile', 'generateBaseline', 'baselineFile', 'logFile'], true)) {
throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s".', $key));
}
}
$normalizedConfiguration += [
'max' => ['total' => 0],
'disabled' => false,
'verbose' => true,
'quiet' => [],
'ignoreFile' => '',
'generateBaseline' => false,
'baselineFile' => '',
'logFile' => null,
];
if ('' === $normalizedConfiguration['disabled'] || filter_var($normalizedConfiguration['disabled'], \FILTER_VALIDATE_BOOLEAN)) {
return self::inDisabledMode();
}
$verboseOutput = [];
foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) {
$verboseOutput[$group] = filter_var($normalizedConfiguration['verbose'], \FILTER_VALIDATE_BOOLEAN);
}
if (\is_array($normalizedConfiguration['quiet'])) {
foreach ($normalizedConfiguration['quiet'] as $shushedGroup) {
$verboseOutput[$shushedGroup] = false;
}
}
return new self(
$normalizedConfiguration['max'],
'',
$verboseOutput,
$normalizedConfiguration['ignoreFile'],
filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN),
$normalizedConfiguration['baselineFile'],
$normalizedConfiguration['logFile']
);
}
public static function inDisabledMode(): self
{
$configuration = new self();
$configuration->enabled = false;
return $configuration;
}
public static function inStrictMode(): self
{
return new self(['total' => 0]);
}
public static function inWeakMode(): self
{
$verboseOutput = [];
foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) {
$verboseOutput[$group] = false;
}
return new self([], '', $verboseOutput);
}
public static function fromNumber($upperBound): self
{
return new self(['total' => $upperBound]);
}
public static function fromRegex($regex): self
{
return new self([], $regex);
}
}