diff --git a/composer.json b/composer.json
index d8e863cc8..40e58869b 100644
--- a/composer.json
+++ b/composer.json
@@ -39,7 +39,6 @@
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^8.5|^9.6",
- "symfony/phpunit-bridge": "^5.2|6.4.25|7.3.3",
"vimeo/psalm": "^4.17"
},
"suggest": {
diff --git a/phpstan.neon b/phpstan.neon
index 91af9c48e..61e18b96c 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -10,6 +10,7 @@ parameters:
excludePaths:
- tests/resources
- tests/Fixtures
+ - src/Util/ClockMock.php
dynamicConstantNames:
- Monolog\Logger::API
bootstrapFiles:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d064f8775..8d5384d66 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -34,10 +34,6 @@
-
-
-
-
diff --git a/psalm.xml.dist b/psalm.xml.dist
index ea60216a6..502f940b3 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -10,6 +10,7 @@
+
diff --git a/src/Util/ClockMock.php b/src/Util/ClockMock.php
new file mode 100644
index 000000000..ea3f18f18
--- /dev/null
+++ b/src/Util/ClockMock.php
@@ -0,0 +1,202 @@
+
+ * @author Dominic Tubach
+ */
+class ClockMock
+{
+ private static $now;
+
+ public static function withClockMock($enable = null): ?bool
+ {
+ if ($enable === null) {
+ return self::$now !== null;
+ }
+
+ self::$now = is_numeric($enable) ? (float) $enable : ($enable ? microtime(true) : null);
+
+ return null;
+ }
+
+ public static function time(): int
+ {
+ if (self::$now === null) {
+ return time();
+ }
+
+ return (int) self::$now;
+ }
+
+ public static function sleep($s): int
+ {
+ if (self::$now === null) {
+ return sleep($s);
+ }
+
+ self::$now += (int) $s;
+
+ return 0;
+ }
+
+ public static function usleep($us): void
+ {
+ if (self::$now === null) {
+ usleep($us);
+ } else {
+ self::$now += $us / 1000000;
+ }
+ }
+
+ /**
+ * @return string|float
+ */
+ public static function microtime($asFloat = false)
+ {
+ if (self::$now === null) {
+ return microtime($asFloat);
+ }
+
+ if ($asFloat) {
+ return self::$now;
+ }
+
+ return \sprintf('%0.6f00 %d', self::$now - (int) self::$now, (int) self::$now);
+ }
+
+ public static function date($format, $timestamp = null): string
+ {
+ if ($timestamp === null) {
+ $timestamp = self::time();
+ }
+
+ return date($format, $timestamp);
+ }
+
+ public static function gmdate($format, $timestamp = null): string
+ {
+ if ($timestamp === null) {
+ $timestamp = self::time();
+ }
+
+ return gmdate($format, $timestamp);
+ }
+
+ /**
+ * @return array|int|float
+ */
+ public static function hrtime($asNumber = false)
+ {
+ $ns = (self::$now - (int) self::$now) * 1000000000;
+
+ if ($asNumber) {
+ $number = \sprintf('%d%d', (int) self::$now, $ns);
+
+ return \PHP_INT_SIZE === 8 ? (int) $number : (float) $number;
+ }
+
+ return [(int) self::$now, (int) $ns];
+ }
+
+ /**
+ * @return false|int
+ */
+ public static function strtotime(string $datetime, ?int $timestamp = null)
+ {
+ if ($timestamp === null) {
+ $timestamp = self::time();
+ }
+
+ return strtotime($datetime, $timestamp);
+ }
+
+ public static function register($class): void
+ {
+ $self = static::class;
+
+ $mockedNs = [substr($class, 0, strrpos($class, '\\'))];
+ if (strpos($class, '\\Tests\\') > 0) {
+ $ns = str_replace('\\Tests\\', '\\', $class);
+ $mockedNs[] = substr($ns, 0, strrpos($ns, '\\'));
+ } elseif (str_starts_with($class, 'Tests\\')) {
+ $mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6);
+ }
+ foreach ($mockedNs as $ns) {
+ if (\function_exists($ns . '\time')) {
+ continue;
+ }
+ eval(<<