MediaWiki REL1_40
ResponseFactory.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Rest;
4
5use HttpStatus;
6use InvalidArgumentException;
9use stdClass;
10use Throwable;
13
18 private const CT_HTML = 'text/html; charset=utf-8';
19 private const CT_JSON = 'application/json';
20
22 private $textFormatters;
23
25 private $showExceptionDetails = false;
26
30 public function __construct( $textFormatters ) {
31 $this->textFormatters = $textFormatters;
32 }
33
41 public function setShowExceptionDetails( bool $showExceptionDetails ): void {
42 $this->showExceptionDetails = $showExceptionDetails;
43 }
44
52 public function encodeJson( $value ) {
53 $json = json_encode( $value,
54 JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE );
55 if ( $json === false ) {
56 throw new JsonEncodingException( json_last_error_msg(), json_last_error() );
57 }
58 return $json;
59 }
60
66 public function create() {
67 return new Response();
68 }
69
77 public function createJson( $value, $contentType = null ) {
78 $contentType ??= self::CT_JSON;
79 $response = new Response( $this->encodeJson( $value ) );
80 $response->setHeader( 'Content-Type', $contentType );
81 return $response;
82 }
83
94 public function createNoContent() {
95 $response = new Response();
96 $response->setStatus( 204 );
97 return $response;
98 }
99
109 public function createPermanentRedirect( $target ) {
110 $response = $this->createRedirectBase( $target );
111 $response->setStatus( 301 );
112 return $response;
113 }
114
125 public function createLegacyTemporaryRedirect( $target ) {
126 $response = $this->createRedirectBase( $target );
127 $response->setStatus( 302 );
128 return $response;
129 }
130
139 public function createTemporaryRedirect( $target ) {
140 $response = $this->createRedirectBase( $target );
141 $response->setStatus( 307 );
142 return $response;
143 }
144
153 public function createSeeOther( $target ) {
154 $response = $this->createRedirectBase( $target );
155 $response->setStatus( 303 );
156 return $response;
157 }
158
169 public function createNotModified() {
170 $response = new Response();
171 $response->setStatus( 304 );
172 return $response;
173 }
174
182 public function createHttpError( $errorCode, array $bodyData = [] ) {
183 if ( $errorCode < 400 || $errorCode >= 600 ) {
184 throw new InvalidArgumentException( 'error code must be 4xx or 5xx' );
185 }
186 $response = $this->createJson( $bodyData + [
187 'httpCode' => $errorCode,
188 'httpReason' => HttpStatus::getMessage( $errorCode )
189 ] );
190 // TODO add link to error code documentation
191 $response->setStatus( $errorCode );
192 return $response;
193 }
194
205 $errorCode,
206 MessageValue $messageValue,
207 array $extraData = []
208 ) {
209 return $this->createHttpError(
210 $errorCode,
211 array_merge( $extraData, $this->formatMessage( $messageValue ) )
212 );
213 }
214
220 public function createFromException( Throwable $exception ) {
221 if ( $exception instanceof LocalizedHttpException ) {
222 $response = $this->createLocalizedHttpError(
223 $exception->getCode(),
224 $exception->getMessageValue(),
225 $exception->getErrorData() + [
226 'errorKey' => $exception->getErrorKey(),
227 ]
228 );
229 } elseif ( $exception instanceof ResponseException ) {
230 return $exception->getResponse();
231 } elseif ( $exception instanceof RedirectException ) {
232 $response = $this->createRedirectBase( $exception->getTarget() );
233 $response->setStatus( $exception->getCode() );
234 } elseif ( $exception instanceof HttpException ) {
235 if ( in_array( $exception->getCode(), [ 204, 304 ], true ) ) {
236 $response = $this->create();
237 $response->setStatus( $exception->getCode() );
238 } else {
239 $response = $this->createHttpError(
240 $exception->getCode(),
241 array_merge(
242 [ 'message' => $exception->getMessage() ],
243 $exception->getErrorData()
244 )
245 );
246 }
247 } elseif ( $this->showExceptionDetails ) {
248 $response = $this->createHttpError( 500, [
249 'message' => 'Error: exception of type ' . get_class( $exception ) . ': '
250 . $exception->getMessage(),
251 'exception' => MWExceptionHandler::getStructuredExceptionData(
252 $exception,
253 MWExceptionHandler::CAUGHT_BY_OTHER
254 )
255 ] );
256 // XXX: should we try to do something useful with ILocalizedException?
257 // XXX: should we try to do something useful with common MediaWiki errors like ReadOnlyError?
258 } else {
259 $response = $this->createHttpError( 500, [
260 'message' => 'Error: exception of type ' . get_class( $exception ),
261 ] );
262 }
263 return $response;
264 }
265
273 public function createFromReturnValue( $value ) {
274 $originalValue = $value;
275 if ( is_scalar( $value ) ) {
276 $data = [ 'value' => $value ];
277 } elseif ( is_array( $value ) || $value instanceof stdClass ) {
278 $data = $value;
279 } else {
280 $type = gettype( $originalValue );
281 if ( $type === 'object' ) {
282 $type = get_class( $originalValue );
283 }
284 throw new InvalidArgumentException( __METHOD__ . ": Invalid return value type $type" );
285 }
286 $response = $this->createJson( $data );
287 return $response;
288 }
289
295 protected function createRedirectBase( $target ) {
296 $response = new Response( $this->getHyperLink( $target ) );
297 $response->setHeader( 'Content-Type', self::CT_HTML );
298 $response->setHeader( 'Location', $target );
299 return $response;
300 }
301
308 protected function getHyperLink( $url ) {
309 $url = htmlspecialchars( $url, ENT_COMPAT );
310 return "<!doctype html><title>Redirect</title><a href=\"$url\">$url</a>";
311 }
312
313 public function formatMessage( MessageValue $messageValue ) {
314 if ( !$this->textFormatters ) {
315 // For unit tests
316 return [];
317 }
318 $translations = [];
319 foreach ( $this->textFormatters as $formatter ) {
320 $lang = LanguageCode::bcp47( $formatter->getLangCode() );
321 $messageText = $formatter->format( $messageValue );
322 $translations[$lang] = $messageText;
323 }
324 return [ 'messageTranslations' => $translations ];
325 }
326
327}
Methods for dealing with language codes.
Handler class for MWExceptions.
This is the base exception class for non-fatal exceptions thrown from REST handlers.
This is an exception class that extends HttpException and will generate a redirect when handled.
This is an exception class that wraps a Response and extends HttpException.
Generates standardized response objects.
encodeJson( $value)
Encode a stdClass object or array to a JSON string.
create()
Create an unspecified response.
createFromException(Throwable $exception)
Turn a throwable into a JSON error response.
formatMessage(MessageValue $messageValue)
createJson( $value, $contentType=null)
Create a successful JSON response.
setShowExceptionDetails(bool $showExceptionDetails)
Control whether web responses may include a exception messager and backtrace.
createTemporaryRedirect( $target)
Creates a temporary (307) redirect.
createHttpError( $errorCode, array $bodyData=[])
Create a HTTP 4xx or 5xx response.
createPermanentRedirect( $target)
Creates a permanent (301) redirect.
createLegacyTemporaryRedirect( $target)
Creates a temporary (302) redirect.
createRedirectBase( $target)
Create a redirect response with type / response code unspecified.
createLocalizedHttpError( $errorCode, MessageValue $messageValue, array $extraData=[])
Create an HTTP 4xx or 5xx response with error message localisation.
createNoContent()
Create a 204 (No Content) response, used to indicate that an operation which does not return anything...
createFromReturnValue( $value)
Create a JSON response from an arbitrary value.
getHyperLink( $url)
Returns a minimal HTML document that links to the given URL, as suggested by RFC 7231 for 3xx respons...
createSeeOther( $target)
Creates a See Other (303) redirect.
createNotModified()
Create a 304 (Not Modified) response, used when the client has an up-to-date cached response.
Value object representing a message for i18n.
if(!isset( $args[0])) $lang