clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,392 @@
<?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 = [], $regex = '', $verboseOutput = [], $ignoreFile = '', $generateBaseline = false, $baselineFile = '', $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));
}
/**
* @param string $message
*/
public function shouldDisplayStackTrace($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($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);
}
}

View File

@@ -0,0 +1,448 @@
<?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;
use Doctrine\Deprecations\Deprecation as DoctrineDeprecation;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Metadata\Api\Groups;
use PHPUnit\Util\Test;
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor;
use Symfony\Component\ErrorHandler\DebugClassLoader;
class_exists(Groups::class);
/**
* @internal
*/
class Deprecation
{
public const PATH_TYPE_VENDOR = 'path_type_vendor';
public const PATH_TYPE_SELF = 'path_type_internal';
public const PATH_TYPE_UNDETERMINED = 'path_type_undetermined';
public const TYPE_SELF = 'type_self';
public const TYPE_DIRECT = 'type_direct';
public const TYPE_INDIRECT = 'type_indirect';
public const TYPE_UNDETERMINED = 'type_undetermined';
private $trace = [];
private $message;
private $languageDeprecation;
private $originClass;
private $originMethod;
private $triggeringFile;
private $triggeringClass;
/** @var string[] Absolute paths to vendor directories */
private static $vendors;
/**
* @var string[] Absolute paths to source or tests of the project, cache
* directories excluded because it is based on autoloading
* rules and cache systems typically do not use those
*/
private static $internalPaths = [];
private $originalFilesStack;
/**
* @param string $message
* @param string $file
* @param bool $languageDeprecation
*/
public function __construct($message, array $trace, $file, $languageDeprecation = false)
{
if (DebugClassLoader::class === ($trace[2]['class'] ?? '')) {
$this->triggeringClass = $trace[2]['args'][0];
}
switch ($trace[2]['function'] ?? '') {
case 'trigger_deprecation':
$file = $trace[2]['file'];
array_splice($trace, 1, 1);
break;
case 'delegateTriggerToBackend':
if (DoctrineDeprecation::class === ($trace[2]['class'] ?? '')) {
$file = $trace[3]['file'];
array_splice($trace, 1, 2);
}
break;
}
$this->trace = $trace;
$this->message = $message;
$this->languageDeprecation = $languageDeprecation;
$i = \count($trace);
while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) {
// No-op
}
$line = $trace[$i];
$this->triggeringFile = $file;
for ($j = 1; $j < $i; ++$j) {
if (!isset($trace[$j]['function'], $trace[1 + $j]['class'], $trace[1 + $j]['args'][0])) {
continue;
}
if ('trigger_error' === $trace[$j]['function'] && !isset($trace[$j]['class'])) {
if (DebugClassLoader::class === $trace[1 + $j]['class']) {
$class = $trace[1 + $j]['args'][0];
$this->triggeringFile = isset($trace[1 + $j]['args'][1]) ? realpath($trace[1 + $j]['args'][1]) : (new \ReflectionClass($class))->getFileName();
$this->getOriginalFilesStack();
array_splice($this->originalFilesStack, 0, $j, [$this->triggeringFile]);
if (preg_match('/(?|"([^"]++)" that is deprecated|should implement method "(?:static )?([^:]++))/', $message, $m) || (false === strpos($message, '()" will return') && false === strpos($message, 'native return type declaration') && preg_match('/^(?:The|Method) "([^":]++)/', $message, $m))) {
$this->triggeringFile = (new \ReflectionClass($m[1]))->getFileName();
array_unshift($this->originalFilesStack, $this->triggeringFile);
}
}
break;
}
}
if (!isset($line['object']) && !isset($line['class'])) {
return;
}
set_error_handler(function () {});
try {
$parsedMsg = unserialize($this->message);
} finally {
restore_error_handler();
}
if ($parsedMsg && isset($parsedMsg['deprecation'])) {
$this->message = $parsedMsg['deprecation'];
$this->originClass = $parsedMsg['class'];
$this->originMethod = $parsedMsg['method'];
if (isset($parsedMsg['files_stack'])) {
$this->originalFilesStack = $parsedMsg['files_stack'];
}
// If the deprecation has been triggered via
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
// then we need to use the serialized information to determine
// if the error has been triggered from vendor code.
if (isset($parsedMsg['triggering_file'])) {
$this->triggeringFile = $parsedMsg['triggering_file'];
}
return;
}
if (!isset($line['class'], $trace[$i - 2]['function']) || 0 !== strpos($line['class'], SymfonyTestsListenerFor::class)) {
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
$this->originMethod = $line['function'];
return;
}
$test = $line['args'][0] ?? null;
if (($test instanceof TestCase || $test instanceof TestSuite) && ('trigger_error' !== $trace[$i - 2]['function'] || isset($trace[$i - 2]['class']))) {
$this->originClass = \get_class($test);
$this->originMethod = $test->getName();
return;
}
}
/**
* @return bool
*/
private function lineShouldBeSkipped(array $line)
{
if (!isset($line['class'])) {
return true;
}
$class = $line['class'];
return 'ReflectionMethod' === $class || 0 === strpos($class, 'PHPUnit\\');
}
/**
* @return bool
*/
public function originatesFromDebugClassLoader()
{
return isset($this->triggeringClass);
}
/**
* @return string
*/
public function triggeringClass()
{
if (null === $this->triggeringClass) {
throw new \LogicException('Check with originatesFromDebugClassLoader() before calling this method.');
}
return $this->triggeringClass;
}
/**
* @return bool
*/
public function originatesFromAnObject()
{
return isset($this->originClass);
}
/**
* @return string
*/
public function originatingClass()
{
if (null === $this->originClass) {
throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
}
$class = $this->originClass;
return false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
}
/**
* @return string
*/
public function originatingMethod()
{
if (null === $this->originMethod) {
throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
}
return $this->originMethod;
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @return bool
*/
public function isLegacy()
{
if (!$this->originClass || (new \ReflectionClass($this->originClass))->isInternal()) {
return false;
}
$method = $this->originatingMethod();
$groups = class_exists(Groups::class, false) ? [new Groups(), 'groups'] : [Test::class, 'getGroups'];
return 0 === strpos($method, 'testLegacy')
|| 0 === strpos($method, 'provideLegacy')
|| 0 === strpos($method, 'getLegacy')
|| strpos($this->originClass, '\Legacy')
|| \in_array('legacy', $groups($this->originClass, $method), true);
}
/**
* @return bool
*/
public function isMuted()
{
if ('Function ReflectionType::__toString() is deprecated' !== $this->message) {
return false;
}
if (isset($this->trace[1]['class'])) {
return 0 === strpos($this->trace[1]['class'], 'PHPUnit\\');
}
return false !== strpos($this->triggeringFile, \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'phpunit'.\DIRECTORY_SEPARATOR);
}
/**
* Tells whether both the calling package and the called package are vendor
* packages.
*
* @return string
*/
public function getType()
{
$pathType = $this->getPathType($this->triggeringFile);
if ($this->languageDeprecation && self::PATH_TYPE_VENDOR === $pathType) {
// the triggering file must be used for language deprecations
return self::TYPE_INDIRECT;
}
if (self::PATH_TYPE_SELF === $pathType) {
return self::TYPE_SELF;
}
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
return self::TYPE_UNDETERMINED;
}
$erroringFile = $erroringPackage = null;
foreach ($this->getOriginalFilesStack() as $file) {
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
continue;
}
if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($file)) {
return self::TYPE_DIRECT;
}
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
return self::TYPE_UNDETERMINED;
}
if (null !== $erroringFile && null !== $erroringPackage) {
$package = $this->getPackage($file);
if ('composer' !== $package && $package !== $erroringPackage) {
return self::TYPE_INDIRECT;
}
continue;
}
$erroringFile = $file;
$erroringPackage = $this->getPackage($file);
}
return self::TYPE_DIRECT;
}
private function getOriginalFilesStack()
{
if (null === $this->originalFilesStack) {
$this->originalFilesStack = [];
foreach ($this->trace as $frame) {
if (!isset($frame['file'], $frame['function']) || (!isset($frame['class']) && \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true))) {
continue;
}
$this->originalFilesStack[] = $frame['file'];
}
}
return $this->originalFilesStack;
}
/**
* getPathType() should always be called prior to calling this method.
*
* @param string $path
*
* @return string
*/
private function getPackage($path)
{
$path = realpath($path) ?: $path;
foreach (self::getVendors() as $vendorRoot) {
if (0 === strpos($path, $vendorRoot)) {
$relativePath = substr($path, \strlen($vendorRoot) + 1);
$vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true);
if (false === $vendor) {
return 'symfony';
}
return rtrim($vendor.'/'.strstr(substr($relativePath, \strlen($vendor) + 1), \DIRECTORY_SEPARATOR, true), '/');
}
}
throw new \RuntimeException(sprintf('No vendors found for path "%s".', $path));
}
/**
* @return string[]
*/
private static function getVendors()
{
if (null === self::$vendors) {
self::$vendors = $paths = [];
self::$vendors[] = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Legacy';
if (class_exists(DebugClassLoader::class, false)) {
self::$vendors[] = \dirname((new \ReflectionClass(DebugClassLoader::class))->getFileName());
}
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = \dirname($r->getFileName(), 2);
if (file_exists($v.'/composer/installed.json')) {
self::$vendors[] = $v;
$loader = require $v.'/autoload.php';
$paths = self::addSourcePathsFromPrefixes(
array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()),
$paths
);
}
}
}
foreach ($paths as $path) {
foreach (self::$vendors as $vendor) {
if (0 !== strpos($path, $vendor)) {
self::$internalPaths[] = $path;
}
}
}
}
return self::$vendors;
}
private static function addSourcePathsFromPrefixes(array $prefixesByNamespace, array $paths): array
{
foreach ($prefixesByNamespace as $prefixes) {
foreach ($prefixes as $prefix) {
if (false !== realpath($prefix)) {
$paths[] = realpath($prefix);
}
}
}
return $paths;
}
/**
* @param string $path
*
* @return string
*/
private function getPathType($path)
{
$realPath = realpath($path);
if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) {
return self::PATH_TYPE_UNDETERMINED;
}
foreach (self::getVendors() as $vendor) {
if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
return self::PATH_TYPE_VENDOR;
}
}
foreach (self::$internalPaths as $internalPath) {
if (0 === strpos($realPath, $internalPath)) {
return self::PATH_TYPE_SELF;
}
}
return self::PATH_TYPE_UNDETERMINED;
}
/**
* @return string
*/
public function toString()
{
$exception = new \Exception($this->message);
$reflection = new \ReflectionProperty($exception, 'trace');
$reflection->setAccessible(true);
$reflection->setValue($exception, $this->trace);
return ($this->originatesFromAnObject() ? 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().":\n" : '')
.$this->message."\n"
."Stack trace:\n"
.str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString())."\n";
}
}

View File

@@ -0,0 +1,68 @@
<?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
*/
final class DeprecationGroup
{
private $count = 0;
/**
* @var DeprecationNotice[] keys are messages
*/
private $deprecationNotices = [];
/**
* @param string $message
* @param string $class
* @param string $method
*/
public function addNoticeFromObject($message, $class, $method)
{
$this->deprecationNotice($message)->addObjectOccurrence($class, $method);
$this->addNotice();
}
/**
* @param string $message
*/
public function addNoticeFromProceduralCode($message)
{
$this->deprecationNotice($message)->addProceduralOccurrence();
$this->addNotice();
}
public function addNotice()
{
++$this->count;
}
/**
* @param string $message
*/
private function deprecationNotice($message): DeprecationNotice
{
return $this->deprecationNotices[$message] ?? $this->deprecationNotices[$message] = new DeprecationNotice();
}
public function count(): int
{
return $this->count;
}
public function notices(): array
{
return $this->deprecationNotices;
}
}

View File

@@ -0,0 +1,49 @@
<?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
*/
final class DeprecationNotice
{
private $count = 0;
/**
* @var int[]
*/
private $countsByCaller = [];
public function addObjectOccurrence($class, $method)
{
if (!isset($this->countsByCaller["$class::$method"])) {
$this->countsByCaller["$class::$method"] = 0;
}
++$this->countsByCaller["$class::$method"];
++$this->count;
}
public function addProceduralOccurrence()
{
++$this->count;
}
public function getCountsByCaller(): array
{
return $this->countsByCaller;
}
public function count(): int
{
return $this->count;
}
}