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 / ViewCacheInvalidator.php

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

161 lines 5.7 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 * Cache invalidation orchestration for admin view queries.
9 *
10 * Manages bulk mutation gating, status count cache invalidation,
11 * view snapshot invalidation, regex cache clearing, and mutation
12 * watermark reads.
13 */
14 class ABJ_404_Solution_ViewCacheInvalidator {
15
16 /** @var ABJ_404_Solution_DatabaseCore */
17 private $dbCore;
18
19 /** @var ABJ_404_Solution_RedirectsRepository */
20 private $redirectsRepo;
21
22 /** @var string */
23 private $viewDoneFreshnessOptionName;
24
25 /** @var ABJ_404_Solution_ViewBuildOrchestratorInterface|null */
26 private $viewBuildOrchestrator;
27
28 /**
29 * @param ABJ_404_Solution_DatabaseCore $dbCore
30 * @param ABJ_404_Solution_RedirectsRepository $redirectsRepo
31 * @param string $viewDoneFreshnessOptionName
32 */
33 public function __construct(
34 ABJ_404_Solution_DatabaseCore $dbCore,
35 ABJ_404_Solution_RedirectsRepository $redirectsRepo,
36 string $viewDoneFreshnessOptionName
37 ) {
38 $this->dbCore = $dbCore;
39 $this->redirectsRepo = $redirectsRepo;
40 $this->viewDoneFreshnessOptionName = $viewDoneFreshnessOptionName;
41 }
42
43 /**
44 * @param ABJ_404_Solution_ViewBuildOrchestratorInterface $viewBuildOrchestrator
45 * @return void
46 */
47 public function setViewBuildOrchestrator(ABJ_404_Solution_ViewBuildOrchestratorInterface $viewBuildOrchestrator): void {
48 $this->viewBuildOrchestrator = $viewBuildOrchestrator;
49 }
50
51 /** @return ABJ_404_Solution_ViewBuildOrchestratorInterface */
52 private function requireViewBuildOrchestrator(): ABJ_404_Solution_ViewBuildOrchestratorInterface {
53 if ($this->viewBuildOrchestrator === null) {
54 throw new \RuntimeException('ViewCacheInvalidator requires ViewBuildOrchestrator (call setViewBuildOrchestrator first)'); // allow-raw-error: assertion, should never reach user
55 }
56 return $this->viewBuildOrchestrator;
57 }
58
59 /** @return int */
60 private function bumpMutationWatermark(): int {
61 if (!class_exists('ABJ_404_Solution_MutationWatermark')) {
62 return 0;
63 }
64 return ABJ_404_Solution_MutationWatermark::bump();
65 }
66
67 /** @return void */
68 public function setSqlBigSelects(): void {
69 $ignoreErrorsOptions = array('log_errors' => false);
70 $this->dbCore->queryAndGetResults("set session max_join_size = 18446744073709551615",
71 $ignoreErrorsOptions);
72 $this->dbCore->queryAndGetResults("set session sql_big_selects = 1", $ignoreErrorsOptions);
73 }
74
75 /**
76 * Open/close the bulk-mutation window.
77 *
78 * @template T
79 * @param callable():T $work
80 * @return T
81 */
82 public function runWithDeferredInvalidation(callable $work) {
83 $prior = ABJ_404_Solution_ViewReadRuntimeState::$bulkMutationInProgress;
84 ABJ_404_Solution_ViewReadRuntimeState::$bulkMutationInProgress = true;
85 try {
86 return $work();
87 } finally {
88 ABJ_404_Solution_ViewReadRuntimeState::$bulkMutationInProgress = $prior;
89 $this->bumpMutationWatermark();
90 }
91 }
92
93 /**
94 * Invalidate cached status counts.
95 * Call this when redirects are created, updated, or deleted.
96 *
97 * @return void
98 */
99 public function invalidateStatusCountsCache(): void {
100 if (ABJ_404_Solution_ViewReadRuntimeState::$bulkMutationInProgress) {
101 return;
102 }
103 delete_transient(ABJ_404_Solution_ViewReadRuntimeState::CACHE_KEY_REDIRECT_STATUS);
104 delete_transient(ABJ_404_Solution_ViewReadRuntimeState::CACHE_KEY_CAPTURED_STATUS);
105 delete_transient(ABJ_404_Solution_ViewReadRuntimeState::CACHE_KEY_HIGH_IMPACT_CAPTURED);
106 $this->invalidateViewSnapshotCache();
107 }
108
109 /**
110 * Clear the view snapshot cache.
111 *
112 * @return void
113 */
114 public function invalidateViewSnapshotCache(): void {
115 if (function_exists('delete_option')) {
116 delete_option($this->viewDoneFreshnessOptionName);
117 }
118 $this->requireViewBuildOrchestrator()->invalidateViewDoneServeableCacheBridge();
119 $this->requireViewBuildOrchestrator()->scheduleViewDoneRebuild();
120
121 $query = "DELETE FROM {wp_abj404_view_cache} WHERE 1=1";
122 $this->dbCore->queryAndGetResults($query, array('log_errors' => false, 'skip_repair' => true));
123
124 global $wpdb;
125 if (isset($wpdb->options) && method_exists($wpdb, 'query')) {
126 /** @var string $optionsTable */
127 // @utf8-audit: opt-out — wpdb->options is a WordPress-controlled table identifier.
128 $optionsTable = esc_sql($wpdb->options);
129 // DAO-bypass-approved: View-cache clear targets wp_options -- outside the plugin's owned tables; runs during cache invalidation hot path; failure is best-effort
130 $wpdb->query(
131 "DELETE FROM `{$optionsTable}` WHERE option_name LIKE '_transient_abj404_view_%'"
132 . " OR option_name LIKE '_transient_timeout_abj404_view_%'"
133 );
134 }
135 }
136
137 /**
138 * @return void
139 */
140 public function clearRegexRedirectsCache(): void {
141 $this->redirectsRepo->clearRegexRedirectsCache();
142 }
143
144 /**
145 * Read the current mutation watermark for the per-blog cache key.
146 *
147 * @return int
148 */
149 public function readMutationWatermarkForCacheKey(): int {
150 if (!class_exists('ABJ_404_Solution_MutationWatermark')) {
151 return 0;
152 }
153 try {
154 return ABJ_404_Solution_MutationWatermark::current();
155 // allow-silent-catch: degraded wpdb (e.g. unit-test mocks lacking prepare()) falls back to "version 0" cache bucket; never propagate a watermark-read fault into the read hot path
156 } catch (\Throwable $e) {
157 return 0;
158 }
159 }
160 }
161