PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / Clock.php

Clock.php in 404 Solution 4.2.0, at includes/Clock.php

160 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Clock interface — the seam every time-dependent class talks to instead
9 * of `time()` / `microtime(true)` / `current_time('timestamp')` directly.
10 *
11 * Production code resolves a `ABJ_404_Solution_SystemClock` from the
12 * service container; tests bind a `ABJ_404_Solution_FrozenClock` so they
13 * can advance virtual time deterministically (no `usleep()`, no global
14 * Brain\Monkey stubs of `time()`). See `docs/clock-injection-audit.md`.
15 *
16 * The four-method surface (`now`, `nowFloat`, `wpNow`, `wpNowMysql`) is the
17 * minimum set the audit identified as covering every time-sensitive call
18 * site in `includes/`. Adding new methods here is a project-wide change —
19 * prefer to keep call sites within this surface.
20 */
21 interface ABJ_404_Solution_Clock {
22 /**
23 * Current Unix epoch in seconds (whole integer). Replaces direct calls
24 * to `time()`. Use for cooldown windows, rate-limit windows, and any
25 * comparison whose precision is per-second.
26 *
27 * @return int
28 */
29 public function now(): int;
30
31 /**
32 * Current Unix epoch with microsecond precision. Replaces direct calls
33 * to `microtime(true)`. Use for sub-second elapsed-time measurements
34 * (deadline timers, performance probes).
35 *
36 * @return float
37 */
38 public function nowFloat(): float;
39
40 /**
41 * WordPress-localized "now" timestamp. Replaces direct calls to
42 * `current_time('timestamp')`. Tests treat this as identical to
43 * `now()` because the WP locale offset is irrelevant when virtual
44 * time is being driven by the test directly.
45 *
46 * @return int
47 */
48 public function wpNow(): int;
49
50 /**
51 * MySQL-formatted "now" string ("Y-m-d H:i:s"). Replaces direct calls
52 * to `current_time('mysql')`.
53 *
54 * @return string
55 */
56 public function wpNowMysql(): string;
57 }
58
59 /**
60 * Default production clock — delegates to PHP's wall-clock and to
61 * WordPress's `current_time()` helper. Stateless and immutable.
62 *
63 * Resolved by `bootstrap.php` for the `'clock'` container service.
64 */
65 final class ABJ_404_Solution_SystemClock implements ABJ_404_Solution_Clock {
66
67 public function now(): int {
68 return time();
69 }
70
71 public function nowFloat(): float {
72 return microtime(true);
73 }
74
75 public function wpNow(): int {
76 if (function_exists('current_time')) {
77 $value = current_time('timestamp');
78 if (is_numeric($value)) {
79 return (int)$value;
80 }
81 }
82 return time();
83 }
84
85 public function wpNowMysql(): string {
86 if (function_exists('current_time')) {
87 $value = current_time('mysql');
88 if (is_string($value)) {
89 return $value;
90 }
91 }
92 return gmdate('Y-m-d H:i:s');
93 }
94 }
95
96 /**
97 * Test-only clock that returns a fixed virtual time controllable by the
98 * test author. Default start epoch is 2023-11-14T22:13:20Z (1700000000) —
99 * any moment well after Y2K38 sanity checks would care about and well
100 * before any test would pick a date that confuses date math.
101 *
102 * Tests advance the clock with `advance($seconds)` to fast-forward
103 * cooldown windows / rate-limit windows / cron windows without sleeping.
104 *
105 * Not registered in the container by default. Tests bind it explicitly:
106 * $clock = new ABJ_404_Solution_FrozenClock();
107 * ABJ_404_Solution_ServiceContainer::getInstance()->set('clock',
108 * function() use ($clock) { return $clock; });
109 */
110 final class ABJ_404_Solution_FrozenClock implements ABJ_404_Solution_Clock {
111
112 /** @var float Virtual epoch seconds (microsecond precision). */
113 private $t;
114
115 /** @param float $startEpoch */
116 public function __construct(float $startEpoch = 1700000000.0) {
117 $this->t = $startEpoch;
118 }
119
120 public function now(): int {
121 return (int)$this->t;
122 }
123
124 public function nowFloat(): float {
125 return $this->t;
126 }
127
128 public function wpNow(): int {
129 return (int)$this->t;
130 }
131
132 public function wpNowMysql(): string {
133 return gmdate('Y-m-d H:i:s', (int)$this->t);
134 }
135
136 /**
137 * Advance virtual time by `$seconds`. Negative values rewind — useful
138 * for tests that need to verify behavior at a known point in the past
139 * (e.g. an expired cooldown that was set before the test "started").
140 *
141 * @param float $seconds
142 * @return void
143 */
144 public function advance(float $seconds): void {
145 $this->t += $seconds;
146 }
147
148 /**
149 * Set virtual time to an absolute epoch. Use for tests that need to
150 * pin time to a specific calendar date (e.g. boundary-of-month cron
151 * tests).
152 *
153 * @param float $epoch
154 * @return void
155 */
156 public function set(float $epoch): void {
157 $this->t = $epoch;
158 }
159 }
160