Configuration.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
  11. /**
  12. * @internal
  13. */
  14. class Configuration
  15. {
  16. /**
  17. * @var int[]
  18. */
  19. private $thresholds;
  20. /**
  21. * @var string
  22. */
  23. private $regex;
  24. /**
  25. * @var bool
  26. */
  27. private $enabled = true;
  28. /**
  29. * @var bool[]
  30. */
  31. private $verboseOutput;
  32. /**
  33. * @var bool
  34. */
  35. private $generateBaseline = false;
  36. /**
  37. * @var string
  38. */
  39. private $baselineFile = '';
  40. /**
  41. * @var array
  42. */
  43. private $baselineDeprecations = [];
  44. /**
  45. * @param int[] $thresholds A hash associating groups to thresholds
  46. * @param string $regex Will be matched against messages, to decide whether to display a stack trace
  47. * @param bool[] $verboseOutput Keyed by groups
  48. * @param bool $generateBaseline Whether to generate or update the baseline file
  49. * @param string $baselineFile The path to the baseline file
  50. */
  51. private function __construct(array $thresholds = [], $regex = '', $verboseOutput = [], $generateBaseline = false, $baselineFile = '')
  52. {
  53. $groups = ['total', 'indirect', 'direct', 'self'];
  54. foreach ($thresholds as $group => $threshold) {
  55. if (!\in_array($group, $groups, true)) {
  56. throw new \InvalidArgumentException(sprintf('Unrecognized threshold "%s", expected one of "%s".', $group, implode('", "', $groups)));
  57. }
  58. if (!is_numeric($threshold)) {
  59. throw new \InvalidArgumentException(sprintf('Threshold for group "%s" has invalid value "%s".', $group, $threshold));
  60. }
  61. $this->thresholds[$group] = (int) $threshold;
  62. }
  63. if (isset($this->thresholds['direct'])) {
  64. $this->thresholds += [
  65. 'self' => $this->thresholds['direct'],
  66. ];
  67. }
  68. if (isset($this->thresholds['indirect'])) {
  69. $this->thresholds += [
  70. 'direct' => $this->thresholds['indirect'],
  71. 'self' => $this->thresholds['indirect'],
  72. ];
  73. }
  74. foreach ($groups as $group) {
  75. if (!isset($this->thresholds[$group])) {
  76. $this->thresholds[$group] = 999999;
  77. }
  78. }
  79. $this->regex = $regex;
  80. $this->verboseOutput = [
  81. 'unsilenced' => true,
  82. 'direct' => true,
  83. 'indirect' => true,
  84. 'self' => true,
  85. 'other' => true,
  86. ];
  87. foreach ($verboseOutput as $group => $status) {
  88. if (!isset($this->verboseOutput[$group])) {
  89. throw new \InvalidArgumentException(sprintf('Unsupported verbosity group "%s", expected one of "%s".', $group, implode('", "', array_keys($this->verboseOutput))));
  90. }
  91. $this->verboseOutput[$group] = (bool) $status;
  92. }
  93. if ($generateBaseline && !$baselineFile) {
  94. throw new \InvalidArgumentException('You cannot use the "generateBaseline" configuration option without providing a "baselineFile" configuration option.');
  95. }
  96. $this->generateBaseline = $generateBaseline;
  97. $this->baselineFile = $baselineFile;
  98. if ($this->baselineFile && !$this->generateBaseline) {
  99. if (is_file($this->baselineFile)) {
  100. $map = json_decode(file_get_contents($this->baselineFile));
  101. foreach ($map as $baseline_deprecation) {
  102. $this->baselineDeprecations[$baseline_deprecation->location][$baseline_deprecation->message] = $baseline_deprecation->count;
  103. }
  104. } else {
  105. throw new \InvalidArgumentException(sprintf('The baselineFile "%s" does not exist.', $this->baselineFile));
  106. }
  107. }
  108. }
  109. /**
  110. * @return bool
  111. */
  112. public function isEnabled()
  113. {
  114. return $this->enabled;
  115. }
  116. /**
  117. * @param DeprecationGroup[] $deprecationGroups
  118. *
  119. * @return bool
  120. */
  121. public function tolerates(array $deprecationGroups)
  122. {
  123. $grandTotal = 0;
  124. foreach ($deprecationGroups as $name => $group) {
  125. if ('legacy' !== $name) {
  126. $grandTotal += $group->count();
  127. }
  128. }
  129. if ($grandTotal > $this->thresholds['total']) {
  130. return false;
  131. }
  132. foreach (['self', 'direct', 'indirect'] as $deprecationType) {
  133. if ($deprecationGroups[$deprecationType]->count() > $this->thresholds[$deprecationType]) {
  134. return false;
  135. }
  136. }
  137. return true;
  138. }
  139. /**
  140. * @return bool
  141. */
  142. public function isBaselineDeprecation(Deprecation $deprecation)
  143. {
  144. if ($deprecation->originatesFromAnObject()) {
  145. $location = $deprecation->originatingClass().'::'.$deprecation->originatingMethod();
  146. } else {
  147. $location = 'procedural code';
  148. }
  149. $message = $deprecation->getMessage();
  150. $result = isset($this->baselineDeprecations[$location][$message]) && $this->baselineDeprecations[$location][$message] > 0;
  151. if ($this->generateBaseline) {
  152. if ($result) {
  153. ++$this->baselineDeprecations[$location][$message];
  154. } else {
  155. $this->baselineDeprecations[$location][$message] = 1;
  156. $result = true;
  157. }
  158. } elseif ($result) {
  159. --$this->baselineDeprecations[$location][$message];
  160. }
  161. return $result;
  162. }
  163. /**
  164. * @return bool
  165. */
  166. public function isGeneratingBaseline()
  167. {
  168. return $this->generateBaseline;
  169. }
  170. public function getBaselineFile()
  171. {
  172. return $this->baselineFile;
  173. }
  174. public function writeBaseline()
  175. {
  176. $map = [];
  177. foreach ($this->baselineDeprecations as $location => $messages) {
  178. foreach ($messages as $message => $count) {
  179. $map[] = [
  180. 'location' => $location,
  181. 'message' => $message,
  182. 'count' => $count,
  183. ];
  184. }
  185. }
  186. file_put_contents($this->baselineFile, json_encode($map, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
  187. }
  188. /**
  189. * @param string $message
  190. *
  191. * @return bool
  192. */
  193. public function shouldDisplayStackTrace($message)
  194. {
  195. return '' !== $this->regex && preg_match($this->regex, $message);
  196. }
  197. /**
  198. * @return bool
  199. */
  200. public function isInRegexMode()
  201. {
  202. return '' !== $this->regex;
  203. }
  204. /**
  205. * @return bool
  206. */
  207. public function verboseOutput($group)
  208. {
  209. return $this->verboseOutput[$group];
  210. }
  211. /**
  212. * @param string $serializedConfiguration an encoded string, for instance
  213. * max[total]=1234&max[indirect]=42
  214. *
  215. * @return self
  216. */
  217. public static function fromUrlEncodedString($serializedConfiguration)
  218. {
  219. parse_str($serializedConfiguration, $normalizedConfiguration);
  220. foreach (array_keys($normalizedConfiguration) as $key) {
  221. if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'generateBaseline', 'baselineFile'], true)) {
  222. throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s".', $key));
  223. }
  224. }
  225. $normalizedConfiguration += [
  226. 'max' => [],
  227. 'disabled' => false,
  228. 'verbose' => true,
  229. 'quiet' => [],
  230. 'generateBaseline' => false,
  231. 'baselineFile' => '',
  232. ];
  233. if ('' === $normalizedConfiguration['disabled'] || filter_var($normalizedConfiguration['disabled'], \FILTER_VALIDATE_BOOLEAN)) {
  234. return self::inDisabledMode();
  235. }
  236. $verboseOutput = [];
  237. foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) {
  238. $verboseOutput[$group] = filter_var($normalizedConfiguration['verbose'], \FILTER_VALIDATE_BOOLEAN);
  239. }
  240. if (\is_array($normalizedConfiguration['quiet'])) {
  241. foreach ($normalizedConfiguration['quiet'] as $shushedGroup) {
  242. $verboseOutput[$shushedGroup] = false;
  243. }
  244. }
  245. return new self(
  246. isset($normalizedConfiguration['max']) ? $normalizedConfiguration['max'] : [],
  247. '',
  248. $verboseOutput,
  249. filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN),
  250. $normalizedConfiguration['baselineFile']
  251. );
  252. }
  253. /**
  254. * @return self
  255. */
  256. public static function inDisabledMode()
  257. {
  258. $configuration = new self();
  259. $configuration->enabled = false;
  260. return $configuration;
  261. }
  262. /**
  263. * @return self
  264. */
  265. public static function inStrictMode()
  266. {
  267. return new self(['total' => 0]);
  268. }
  269. /**
  270. * @return self
  271. */
  272. public static function inWeakMode()
  273. {
  274. $verboseOutput = [];
  275. foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) {
  276. $verboseOutput[$group] = false;
  277. }
  278. return new self([], '', $verboseOutput);
  279. }
  280. /**
  281. * @return self
  282. */
  283. public static function fromNumber($upperBound)
  284. {
  285. return new self(['total' => $upperBound]);
  286. }
  287. /**
  288. * @return self
  289. */
  290. public static function fromRegex($regex)
  291. {
  292. return new self([], $regex);
  293. }
  294. }