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 / ajax / EncodedJsonResponse.php

EncodedJsonResponse.php in 404 Solution trunk, at includes/ajax/EncodedJsonResponse.php

117 lines 4.1 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 * The outcome of encoding one AJAX response body: a JSON string that is always
9 * a string, plus how much had to be given up to get one.
10 *
11 * A value object rather than a string return, because "what did we send" and
12 * "did we have to degrade to send it" are two answers and the caller needs
13 * both: the emitter echoes the first and journals the second. A bare string
14 * return forces the caller to re-derive the degradation from json_last_error(),
15 * which by then reflects whichever encode attempt ran last.
16 *
17 * The invariant this type exists to carry: json() is NEVER false. PHP's
18 * json_encode() returns false on a payload it cannot represent, `echo false`
19 * writes zero bytes, and an HTTP 200 with Content-type: application/json and an
20 * empty body is what a browser reports as `parsererror` -- a table that never
21 * renders, with nothing on the server naming why. Making the failure
22 * unrepresentable in the type is what stops that shape coming back.
23 *
24 * @since 4.3.5
25 */
26 final class ABJ_404_Solution_EncodedJsonResponse {
27
28 /** json_encode() succeeded on the first, unmodified attempt. */
29 const STRATEGY_DIRECT = 'direct';
30
31 /** Re-encoded with JSON_INVALID_UTF8_SUBSTITUTE; malformed bytes became U+FFFD. */
32 const STRATEGY_UTF8_SUBSTITUTED = 'utf8_substituted';
33
34 /** Re-encoded with JSON_PARTIAL_OUTPUT_ON_ERROR; unrepresentable branches became null. */
35 const STRATEGY_PARTIAL_OUTPUT = 'partial_output';
36
37 /** Nothing encoded; json() carries a hand-built error envelope instead of the payload. */
38 const STRATEGY_ERROR_ENVELOPE = 'error_envelope';
39
40 /** @var string */
41 private $json;
42
43 /** @var string */
44 private $strategy;
45
46 /** @var int */
47 private $errorCode;
48
49 /** @var string */
50 private $errorMessage;
51
52 /**
53 * @param string $json The bytes to echo. Always a string.
54 * @param string $strategy One of the STRATEGY_* constants.
55 * @param int $errorCode json_last_error() from the FIRST failing attempt, JSON_ERROR_NONE when none failed.
56 * @param string $errorMessage json_last_error_msg() from that same attempt, '' when none failed.
57 */
58 public function __construct(string $json, string $strategy, int $errorCode = JSON_ERROR_NONE, string $errorMessage = '') {
59 $this->json = $json;
60 $this->strategy = $strategy;
61 $this->errorCode = $errorCode;
62 $this->errorMessage = $errorMessage;
63 }
64
65 /** The response body. Never false, never null. */
66 public function json(): string {
67 return $this->json;
68 }
69
70 /** One of the STRATEGY_* constants. */
71 public function strategy(): string {
72 return $this->strategy;
73 }
74
75 /** Whether anything at all had to be given up to produce json(). */
76 public function isDegraded(): bool {
77 return $this->strategy !== self::STRATEGY_DIRECT;
78 }
79
80 /**
81 * Whether the caller's payload reached the browser at all.
82 *
83 * Distinct from isDegraded(): a substituted or partial encode still carries
84 * the table the user asked for, so the admin screen works and the event is
85 * a warning. An error envelope does not, so the admin sees a real message
86 * naming the real reason.
87 */
88 public function carriesPayload(): bool {
89 return $this->strategy !== self::STRATEGY_ERROR_ENVELOPE;
90 }
91
92 /** json_last_error() from the first failing attempt; JSON_ERROR_NONE when nothing failed. */
93 public function errorCode(): int {
94 return $this->errorCode;
95 }
96
97 /** json_last_error_msg() from the first failing attempt; '' when nothing failed. */
98 public function errorMessage(): string {
99 return $this->errorMessage;
100 }
101
102 /**
103 * The fields a diagnostic record carries about this encode.
104 *
105 * @return array<string, mixed>
106 */
107 public function diagnosticFields(): array {
108 return array(
109 'bytes' => strlen($this->json),
110 'hash' => md5($this->json),
111 'encode_strategy' => $this->strategy,
112 'json_last_error' => $this->errorCode,
113 'json_last_error_msg' => $this->errorMessage,
114 );
115 }
116 }
117