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

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

512 lines 18.0 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 * Staged view-build pipeline and mutation watermark system.
9 *
10 * Extracted from DataAccess in Phase 7 of the DataAccess refactor.
11 * Absorbs 13 traits that previously composed into DataAccess.
12 *
13 * @see docs/dataaccess-refactor-plan.md Phase 7.
14 */
15 class ABJ_404_Solution_ViewBuildOrchestrator implements ABJ_404_Solution_ViewBuildOrchestratorInterface {
16
17 // --- Dependencies (constructor injection) ---
18
19 /** @var ABJ_404_Solution_DatabaseCore */
20 private $dbCore;
21
22 /** @var ABJ_404_Solution_Functions */
23 private $f;
24
25 /** @var ABJ_404_Solution_Logging */
26 private $logger;
27 /** @var ABJ_404_Solution_RebuildHealthState|null */
28 private $rebuildHealth;
29
30 // --- Setter-injected dependencies (circular reference resolution) ---
31
32 /** @var ABJ_404_Solution_ViewReadService|null */
33 private $viewReadService;
34
35 /** @var ABJ_404_Solution_LogsRepository|null */
36 private $logsRepo;
37
38 /** @var array<string, ABJ_404_Solution_ViewBuildCollaborator> */
39 private $collaborators = array();
40 /** @var ABJ_404_Solution_ViewQueriesStaged */
41 private $queries;
42 /** @var ABJ_404_Solution_ViewBuildHelpers */
43 private $helpers;
44 /** @var ABJ_404_Solution_ViewBuildLockAndCron */
45 private $lockAndCron;
46 /** @var ABJ_404_Solution_ViewBuildPhpEnvProbe */
47 private $phpEnvProbe;
48 /** @var ABJ_404_Solution_ViewBuildSessionEnvProbe */
49 private $sessionEnvProbe;
50 /** @var ABJ_404_Solution_ViewBuildHostFailurePolicy */
51 private $hostFailurePolicy;
52 /** @var ABJ_404_Solution_ViewBuildForceRestart */
53 private $forceRestart;
54 /** @var ABJ_404_Solution_MutationWatermarkSeam */
55 private $mutationWatermark;
56 /** @var ABJ_404_Solution_AdminMutationGate */
57 private $adminMutationGate;
58
59 /**
60 * @param ABJ_404_Solution_DatabaseCore $dbCore
61 * @param ABJ_404_Solution_Functions|null $f Falls back to abj_service('functions')
62 * @param ABJ_404_Solution_Logging|null $logger Falls back to abj_service('logging')
63 * @param ABJ_404_Solution_RebuildHealthState|null $rebuildHealth shared rebuild health gate
64 */
65 public function __construct(
66 ABJ_404_Solution_DatabaseCore $dbCore,
67 $f = null,
68 $logger = null,
69 $rebuildHealth = null
70 ) {
71 $this->dbCore = $dbCore;
72 $this->f = $f !== null ? $f : abj_service('functions');
73 $this->logger = $logger !== null ? $logger : abj_service('logging');
74 $this->rebuildHealth = $rebuildHealth instanceof ABJ_404_Solution_RebuildHealthState
75 ? $rebuildHealth
76 : $this->resolveRebuildHealthState();
77 $this->queries = new ABJ_404_Solution_ViewQueriesStaged($this);
78 $this->helpers = new ABJ_404_Solution_ViewBuildHelpers($this);
79 $this->lockAndCron = new ABJ_404_Solution_ViewBuildLockAndCron($this);
80 $this->phpEnvProbe = new ABJ_404_Solution_ViewBuildPhpEnvProbe($this);
81 $this->sessionEnvProbe = new ABJ_404_Solution_ViewBuildSessionEnvProbe($this);
82 $this->hostFailurePolicy = new ABJ_404_Solution_ViewBuildHostFailurePolicy($this);
83 $this->forceRestart = new ABJ_404_Solution_ViewBuildForceRestart($this);
84 $this->mutationWatermark = new ABJ_404_Solution_MutationWatermarkSeam($this);
85 $this->adminMutationGate = new ABJ_404_Solution_AdminMutationGate($this);
86 $this->collaborators = array(
87 'queries' => $this->queries,
88 'stage_runner' => new ABJ_404_Solution_ViewBuildStageRunner($this),
89 'stage_callbacks' => new ABJ_404_Solution_ViewBuildStageCallbacks($this),
90 'adaptive' => new ABJ_404_Solution_ViewBuildAdaptive($this),
91 'helpers' => $this->helpers,
92 'started_watermark' => new ABJ_404_Solution_ViewBuildStartedWatermark($this),
93 'lock_and_cron' => $this->lockAndCron,
94 'php_env_probe' => $this->phpEnvProbe,
95 'session_env_probe' => $this->sessionEnvProbe,
96 'host_failure_policy' => $this->hostFailurePolicy,
97 'force_restart' => $this->forceRestart,
98 'mutation_watermark' => $this->mutationWatermark,
99 'admin_mutation_gate' => $this->adminMutationGate,
100 );
101 }
102
103 /** @return ABJ_404_Solution_RebuildHealthState|null */
104 private function resolveRebuildHealthState() {
105 if (class_exists('ABJ_404_Solution_ServiceContainer')
106 && ABJ_404_Solution_ServiceContainer::safeHas('rebuild_health')) {
107 $service = ABJ_404_Solution_ServiceContainer::safeGet('rebuild_health');
108 if ($service instanceof ABJ_404_Solution_RebuildHealthState) {
109 return $service;
110 }
111 }
112 return null;
113 }
114
115 /** @return void */
116 public static function resetViewBuildOncePerRequestGuard(): void {
117 ABJ_404_Solution_ViewQueriesStaged::resetViewBuildOncePerRequestGuard();
118 ABJ_404_Solution_ViewBuildStageRunner::resetViewBuildShutdownLoggerRegistration();
119 }
120
121 /** @return void */
122 public static function resetViewBuildLockFallbackMemos(): void {
123 ABJ_404_Solution_ViewBuildLockAndCron::resetViewBuildLockFallbackMemos();
124 }
125
126 // --- Public ViewBuildOrchestratorInterface delegation ---
127
128 /** @return void */
129 public function claimForegroundViewBuildLease(): void {
130 $this->queries->claimForegroundViewBuildLease();
131 }
132
133 /** @param string $sub @param array<string, mixed> $tableOptions @return array<int, array<string, mixed>> */
134 public function runRedirectsForViewStaged(string $sub, array $tableOptions): array {
135 return $this->queries->runRedirectsForViewStaged($sub, $tableOptions);
136 }
137
138 /** @return bool */
139 public function viewDoneIsServeable(): bool {
140 return $this->queries->viewDoneIsServeable();
141 }
142
143 /** @return int */
144 public function getViewDoneBuiltAtTimestamp(): int {
145 return $this->queries->getViewDoneBuiltAtTimestamp();
146 }
147
148 /** @return void */
149 public function markViewDoneBuildCompleted(): void {
150 $this->queries->markViewDoneBuildCompleted();
151 }
152
153 /** @return array<string, mixed> */
154 public function getViewBuildProgress(): array {
155 return $this->queries->getViewBuildProgress();
156 }
157
158 /** @param bool $forceRebuild @return array<string, mixed> */
159 public function advanceViewBuildOnce(bool $forceRebuild = false): array {
160 return $this->queries->advanceViewBuildOnce($forceRebuild);
161 }
162
163 /** @return array{ran:bool, reason:string, progress:array<string,mixed>} */
164 public function runPageLoadFallbackAdvance(): array {
165 return $this->queries->runPageLoadFallbackAdvance();
166 }
167
168 /** @param string $sub @param array<string, mixed> $tableOptions @return int */
169 public function runRedirectsForViewCountStaged(string $sub, array $tableOptions): int {
170 return $this->queries->runRedirectsForViewCountStaged($sub, $tableOptions);
171 }
172
173 /** @return void */
174 public function rebuildViewDoneInBackground(): void {
175 $this->queries->rebuildViewDoneInBackground();
176 }
177
178 /** @return string */
179 public function reconcileStagedTablesAtRunnerStartup(): string {
180 return $this->queries->reconcileStagedTablesAtRunnerStartup();
181 }
182
183 /** @param string $optionName @param mixed $expected @return bool */
184 public function verifyOptionWriteCoherent(string $optionName, $expected): bool {
185 return $this->helpers->verifyOptionWriteCoherent($optionName, $expected);
186 }
187
188 /** @return void */
189 public function capturePrefixAtBuildStart(): void {
190 $this->helpers->capturePrefixAtBuildStart();
191 }
192
193 /** @return bool */
194 public function verifyPrefixUnchangedSinceStageOne(): bool {
195 return $this->helpers->verifyPrefixUnchangedSinceStageOne();
196 }
197
198 /** @return void */
199 public function clearPrefixAtStageOne(): void {
200 $this->helpers->clearPrefixAtStageOne();
201 }
202
203 /** @return array<string, mixed> */
204 public function probeSqlModeForBuild(): array {
205 return $this->helpers->probeSqlModeForBuild();
206 }
207
208 /** @return array<string, mixed> */
209 public function detectAndAdjustSqlMode(): array {
210 return $this->helpers->detectAndAdjustSqlMode();
211 }
212
213 /** @param string $url @param int $maxLength @return string */
214 public function sanitizeUrlBeforeInsert(string $url, int $maxLength = 0): string {
215 return $this->helpers->sanitizeUrlBeforeInsert($url, $maxLength);
216 }
217
218 /** @return bool */
219 public function verifyBuildLockSerializesWriter(): bool {
220 return $this->lockAndCron->verifyBuildLockSerializesWriter();
221 }
222
223 /** @param int $delaySeconds @return void */
224 public function scheduleViewDoneRebuild(int $delaySeconds = 1): void {
225 $this->lockAndCron->scheduleViewDoneRebuild($delaySeconds);
226 }
227
228 /** @return array<string, mixed> */
229 public function probePhpEnvironmentForBuild(): array {
230 return $this->phpEnvProbe->probePhpEnvironmentForBuild();
231 }
232
233 /** @return bool */
234 public function probeSetTimeLimitAvailability(): bool {
235 return $this->phpEnvProbe->probeSetTimeLimitAvailability();
236 }
237
238 /** @return int */
239 public function probeMemoryLimitForS9(): int {
240 return $this->phpEnvProbe->probeMemoryLimitForS9();
241 }
242
243 /** @return array<string, mixed> */
244 public function probeFilesystemEnvironmentForBuild(): array {
245 return $this->phpEnvProbe->probeFilesystemEnvironmentForBuild();
246 }
247
248 /** @return void */
249 public function clearStagedBuildDegradedState(): void {
250 $this->hostFailurePolicy->clearStagedBuildDegradedState();
251 }
252
253 /** @return bool */
254 public function reconcilePostStageElevenState(): bool {
255 return $this->hostFailurePolicy->reconcilePostStageElevenState();
256 }
257
258 /** @return array<string, mixed> */
259 public function probeSessionVariablesAtS1Entry(): array {
260 return $this->sessionEnvProbe->probeSessionVariablesAtS1Entry();
261 }
262
263 /** @return void */
264 public function markViewDoneInvalidatedByAdminMutation(): void {
265 $this->adminMutationGate->markViewDoneInvalidatedByAdminMutation();
266 }
267
268 /** @param int $lockTimeoutSeconds @return bool */
269 public function forceRestartViewBuild(int $lockTimeoutSeconds = 10): bool {
270 return $this->forceRestart->forceRestartViewBuild($lockTimeoutSeconds);
271 }
272
273 /** @return int */
274 public function bumpMutationWatermark(): int {
275 return $this->mutationWatermark->bumpMutationWatermark();
276 }
277
278 /**
279 * @param ABJ_404_Solution_ViewReadService $viewReadService
280 * @return void
281 */
282 public function setViewReadService(ABJ_404_Solution_ViewReadService $viewReadService): void {
283 $this->viewReadService = $viewReadService;
284 }
285
286 /**
287 * @param ABJ_404_Solution_LogsRepository $logsRepo
288 * @return void
289 */
290 public function setLogsRepository(ABJ_404_Solution_LogsRepository $logsRepo): void {
291 $this->logsRepo = $logsRepo;
292 }
293
294 /** @return ABJ_404_Solution_ViewReadService */
295 private function requireViewReadService(): ABJ_404_Solution_ViewReadService {
296 if ($this->viewReadService === null) {
297 throw new \RuntimeException('ViewBuildOrchestrator requires ViewReadService (call setViewReadService first)');
298 }
299 return $this->viewReadService;
300 }
301
302 /** @return ABJ_404_Solution_LogsRepository */
303 private function requireLogsRepo(): ABJ_404_Solution_LogsRepository {
304 if ($this->logsRepo === null) {
305 throw new \RuntimeException('ViewBuildOrchestrator requires LogsRepository (call setLogsRepository first)');
306 }
307 return $this->logsRepo;
308 }
309
310 /**
311 * Route private cross-collaborator calls through the orchestrator host.
312 *
313 * @param string $name
314 * @param array<int, mixed> $arguments
315 * @return mixed
316 */
317 public function __call(string $name, array $arguments) {
318 if (method_exists($this, $name)) {
319 return $this->invokeMethod($this, $name, $arguments);
320 }
321 return $this->invokeCollaborator($name, $arguments);
322 }
323
324 /**
325 * @param string $name
326 * @return mixed
327 */
328 public function __get(string $name) {
329 $publicProperties = get_object_vars($this);
330 if (array_key_exists($name, $publicProperties)) {
331 return $publicProperties[$name];
332 }
333 if (property_exists($this, $name)) {
334 return $this->$name;
335 }
336 foreach ($this->collaborators as $collaborator) {
337 if (property_exists($collaborator, $name)) {
338 $property = new \ReflectionProperty($collaborator, $name);
339 return $property->getValue($collaborator);
340 }
341 }
342 throw new \RuntimeException('Unknown ViewBuildOrchestrator property: ' . $name);
343 }
344
345 /**
346 * @param string $name
347 * @param mixed $value
348 * @return void
349 */
350 public function __set(string $name, $value): void {
351 $publicProperties = get_object_vars($this);
352 if (array_key_exists($name, $publicProperties)) {
353 $this->$name = $value;
354 return;
355 }
356 if (property_exists($this, $name)) {
357 $this->$name = $value;
358 return;
359 }
360 foreach ($this->collaborators as $collaborator) {
361 if (property_exists($collaborator, $name)) {
362 $property = new \ReflectionProperty($collaborator, $name);
363 $property->setValue($collaborator, $value);
364 return;
365 }
366 }
367 throw new \RuntimeException('Unknown ViewBuildOrchestrator property: ' . $name);
368 }
369
370 /**
371 * @param string $name
372 * @param array<int, mixed> $arguments
373 * @return mixed
374 */
375 private function invokeCollaborator(string $name, array $arguments = array()) {
376 foreach ($this->collaborators as $collaborator) {
377 if (method_exists($collaborator, $name)) {
378 return $this->invokeMethod($collaborator, $name, $arguments);
379 }
380 }
381 throw new \BadMethodCallException('Unknown ViewBuildOrchestrator method: ' . $name);
382 }
383
384 /**
385 * @param object $target
386 * @param string $name
387 * @param array<int, mixed> $arguments
388 * @return mixed
389 */
390 private function invokeMethod($target, string $name, array $arguments) {
391 $method = new \ReflectionMethod($target, $name);
392 return $method->invokeArgs($target, $arguments);
393 }
394
395 // --- Bridge methods for ViewReadService (interface contract) ---
396
397 /** @return void */
398 public function invalidateViewDoneServeableCacheBridge(): void {
399 $this->queries->invalidateViewDoneServeableCache();
400 }
401
402 /** @return array<string, mixed> */
403 public function getStagedQueryOptionsForRead(): array {
404 return $this->helpers->stagedQueryOptions();
405 }
406
407 /**
408 * @param string $shortName
409 * @param int $default
410 * @return int
411 */
412 public function readBuildProgressOption(string $shortName, int $default = 0): int {
413 return $this->helpers->readProgressOption($shortName, $default);
414 }
415
416 // --- Delegation methods for external dependencies the traits call via $this-> ---
417
418 /**
419 * @param string $query
420 * @param array<string, mixed> $options
421 * @return array<string, mixed>
422 */
423 public function queryAndGetResults($query, $options = array()) {
424 return $this->dbCore->queryAndGetResults($query, $options);
425 }
426
427 /** @param string $query @return string */
428 public function doTableNameReplacements($query): string {
429 return $this->dbCore->doTableNameReplacements($query);
430 }
431
432 /** @return string */
433 public function getLowercasePrefix(): string {
434 return $this->dbCore->getLowercasePrefix();
435 }
436
437 /** @return void */
438 public function ensureConnection(): void {
439 $this->dbCore->ensureConnection();
440 }
441
442 /** @return ABJ_404_Solution_Clock */
443 protected function clock(): ABJ_404_Solution_Clock {
444 return $this->dbCore->clock();
445 }
446
447 /**
448 * @param int $stageNumber
449 * @param string $errorText
450 * @return string
451 */
452 public function classifyStageFailure(int $stageNumber, string $errorText): string {
453 return $this->dbCore->classifyStageFailure($stageNumber, $errorText);
454 }
455
456 /** @param string $errorText @return bool */
457 public function isResumableStagedKill(string $errorText): bool {
458 return $this->dbCore->isResumableStagedKill($errorText);
459 }
460
461 /** @param string|null $errorText @return bool */
462 public function isTransientConnectionError(?string $errorText): bool {
463 return $this->dbCore->isTransientConnectionError($errorText);
464 }
465
466 /**
467 * @param string $tableName
468 * @param string $columnName
469 * @return string
470 */
471 public function getColumnCollationString(string $tableName, string $columnName): string {
472 return $this->dbCore->getColumnCollationString($tableName, $columnName);
473 }
474
475 // --- Delegation methods for ViewReadService methods the traits call ---
476
477 /** @return array<string, string> */
478 public function viewBuildOnlyTranslations(): array {
479 return $this->requireViewReadService()->viewBuildOnlyTranslations();
480 }
481
482 /**
483 * @param string $sub
484 * @param array<string, mixed> $tableOptions
485 * @return array<int, array<string, mixed>>
486 */
487 public function readFromViewDone(string $sub, array $tableOptions): array {
488 return $this->requireViewReadService()->readFromViewDone($sub, $tableOptions);
489 }
490
491 /** @return array<string, int> */
492 public function getViewBuildProgressFingerprint(): array {
493 return $this->requireViewReadService()->getViewBuildProgressFingerprint();
494 }
495
496 /**
497 * @param string $sub
498 * @param array<string, mixed> $tableOptions
499 * @return string
500 */
501 public function buildViewDoneCountQuery(string $sub, array $tableOptions): string {
502 return $this->requireViewReadService()->buildViewDoneCountQuery($sub, $tableOptions);
503 }
504
505 // --- Delegation for LogsRepository ---
506
507 /** @return bool */
508 public function logsHitsTableExists() {
509 return $this->requireLogsRepo()->logsHitsTableExists();
510 }
511 }
512