PluginProbe
404 Solution / trunk
404 Solution vtrunk
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 / view-build / ViewReadOutcome.php

ViewReadOutcome.php in 404 Solution trunk, at includes/view-build/ViewReadOutcome.php

120 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 * Classifies the outcome of an admin view row read (getRedirectsForView).
9 *
10 * A row read can return an empty array for three structurally different
11 * reasons, and the admin UI must treat them differently:
12 *
13 * - COMPLETE the source genuinely has no rows for this page; "No
14 * records" is the truthful thing to render.
15 * - PENDING the staged view_done build is not serveable yet.
16 * - ERRORED the staged read threw.
17 * - STALE_EMPTY the served snapshot returned zero rows while the live
18 * source count says rows exist for the requested page --
19 * the "count shows thousands but the table is empty"
20 * release blocker (i455).
21 *
22 * Only COMPLETE is trustworthy; the other three are "incomplete" and the
23 * renderer shows a still-preparing state while the AJAX endpoint re-engages
24 * the view-build poller. This class owns that small state machine plus the
25 * pure stale-empty heuristic so the decision lives in one independently
26 * testable place rather than spread across the read coordinator.
27 */
28 class ABJ_404_Solution_ViewReadOutcome {
29
30 /** A read outcome has not been recorded yet this request. */
31 const STATUS_UNKNOWN = 'unknown';
32 /** The rows returned are a trustworthy listing (possibly a genuine empty set). */
33 const STATUS_COMPLETE = 'complete';
34 /** The staged view_done build is not serveable yet; rows could not be read. */
35 const STATUS_PENDING = 'pending';
36 /** The staged read threw; rows could not be read. */
37 const STATUS_ERRORED = 'errored';
38 /** Empty rows while the live source count says rows exist for this page. */
39 const STATUS_STALE_EMPTY = 'stale_empty';
40
41 /** @var string One of the STATUS_* constants. */
42 private $status = self::STATUS_UNKNOWN;
43
44 /** @return void */
45 public function markPending(): void {
46 $this->status = self::STATUS_PENDING;
47 }
48
49 /** @return void */
50 public function markErrored(): void {
51 $this->status = self::STATUS_ERRORED;
52 }
53
54 /**
55 * Classify a successful (non-throwing) row read. A non-empty result is
56 * always trustworthy; an empty result is trustworthy only when the live
57 * source count agrees there are no rows for the requested page. The count
58 * is probed (via $liveCountProbe, expected to return -1 when unavailable)
59 * only when the rows are empty. Strict-mode reads
60 * (`_abj404_throw_on_view_query_error`) classify pending/error themselves
61 * and are never probed here.
62 *
63 * @param array<int|string, mixed> $rows
64 * @param array<string, mixed> $tableOptions
65 * @param callable():int $liveCountProbe
66 * @return void
67 */
68 public function classifyRows(array $rows, array $tableOptions, callable $liveCountProbe): void {
69 if (!empty($rows) || !empty($tableOptions['_abj404_throw_on_view_query_error'])) {
70 // Non-empty rows are trustworthy; strict-mode empties are the
71 // caller's own (already-classified) pending/error responsibility.
72 $this->status = self::STATUS_COMPLETE;
73 return;
74 }
75 $this->status = self::emptyRowsAreStale((int)$liveCountProbe(), $tableOptions)
76 ? self::STATUS_STALE_EMPTY
77 : self::STATUS_COMPLETE;
78 }
79
80 /**
81 * Pure heuristic: do empty rows contradict the live source count? True
82 * when the count says rows should appear on the requested page yet none
83 * were returned. An empty page past the end of a real result set
84 * (offset >= count) is a legitimate empty, not a stale snapshot.
85 *
86 * @param int $liveCount negative or zero means "no rows / unavailable"
87 * @param array<string, mixed> $tableOptions supplies perpage/paged
88 * @return bool
89 */
90 public static function emptyRowsAreStale(int $liveCount, array $tableOptions): bool {
91 if ($liveCount <= 0) {
92 return false;
93 }
94 $perpage = isset($tableOptions['perpage']) && is_numeric($tableOptions['perpage'])
95 ? max(1, intval($tableOptions['perpage'])) : 25;
96 $paged = isset($tableOptions['paged']) && is_numeric($tableOptions['paged'])
97 ? max(1, intval($tableOptions['paged'])) : 1;
98 return $liveCount > ($paged - 1) * $perpage;
99 }
100
101 /**
102 * Whether the last classified read is NOT a trustworthy "genuinely empty"
103 * listing (pending, errored, or stale-empty).
104 *
105 * @return bool
106 */
107 public function wasIncomplete(): bool {
108 return in_array($this->status, array(
109 self::STATUS_PENDING,
110 self::STATUS_ERRORED,
111 self::STATUS_STALE_EMPTY,
112 ), true);
113 }
114
115 /** @return string One of the STATUS_* constants. */
116 public function status(): string {
117 return $this->status;
118 }
119 }
120