These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Elgg\Http; |
||
4 | |||
5 | use Elgg\Ajax\Service as AjaxService; |
||
6 | use Elgg\PluginHooksService; |
||
7 | use ElggEntity; |
||
8 | use InvalidArgumentException; |
||
9 | use InvalidParameterException; |
||
10 | use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse; |
||
11 | use Symfony\Component\HttpFoundation\Response; |
||
12 | use Symfony\Component\HttpFoundation\ResponseHeaderBag; |
||
13 | |||
14 | /** |
||
15 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
16 | * |
||
17 | * @since 2.3 |
||
18 | * @access private |
||
19 | */ |
||
20 | class ResponseFactory { |
||
21 | |||
22 | /** |
||
23 | * @var Request |
||
24 | */ |
||
25 | private $request; |
||
26 | |||
27 | /** |
||
28 | * @var AjaxService |
||
29 | */ |
||
30 | private $ajax; |
||
31 | |||
32 | /** |
||
33 | * @var PluginHooksService |
||
34 | */ |
||
35 | private $hooks; |
||
36 | |||
37 | /** |
||
38 | * @var ResponseTransport |
||
39 | */ |
||
40 | private $transport; |
||
41 | |||
42 | /** |
||
43 | * @var Response|bool |
||
44 | */ |
||
45 | private $response_sent = false; |
||
46 | |||
47 | /** |
||
48 | * @var ResponseHeaderBag |
||
49 | */ |
||
50 | private $headers; |
||
51 | |||
52 | /** |
||
53 | * Constructor |
||
54 | * |
||
55 | * @param Request $request HTTP request |
||
56 | * @param PluginHooksService $hooks Plugin hooks service |
||
57 | * @param AjaxService $ajax AJAX service |
||
58 | * @param ResponseTransport $transport Response transport |
||
59 | */ |
||
60 | 133 | public function __construct(Request $request, PluginHooksService $hooks, AjaxService $ajax, ResponseTransport $transport) { |
|
61 | 133 | $this->request = $request; |
|
62 | 133 | $this->hooks = $hooks; |
|
63 | 133 | $this->ajax = $ajax; |
|
64 | 133 | $this->transport = $transport; |
|
65 | 133 | $this->headers = new ResponseHeaderBag(); |
|
66 | 133 | } |
|
67 | |||
68 | /** |
||
69 | * Sets headers to apply to all responses being sent |
||
70 | * |
||
71 | * @param string $name Header name |
||
72 | * @param string $value Header value |
||
73 | * @param bool $replace Replace existing headers |
||
74 | * @return void |
||
75 | */ |
||
76 | 17 | public function setHeader($name, $value, $replace = true) { |
|
77 | 17 | $this->headers->set($name, $value, $replace); |
|
78 | 17 | } |
|
79 | |||
80 | /** |
||
81 | * Get headers set to apply to all responses |
||
82 | * |
||
83 | * @param bool $remove_existing Remove existing headers found in headers_list() |
||
84 | * @return ResponseHeaderBag |
||
85 | */ |
||
86 | 60 | public function getHeaders($remove_existing = true) { |
|
87 | // Add headers that have already been set by underlying views |
||
88 | // e.g. viewtype page shells set content-type headers |
||
89 | 60 | $headers_list = headers_list(); |
|
90 | 60 | foreach ($headers_list as $header) { |
|
91 | if (stripos($header, 'HTTP/1.1') !== false) { |
||
92 | continue; |
||
93 | } |
||
94 | |||
95 | list($name, $value) = explode(':', $header, 2); |
||
96 | $this->setHeader($name, ltrim($value), false); |
||
97 | if ($remove_existing) { |
||
98 | header_remove($name); |
||
99 | } |
||
100 | } |
||
101 | |||
102 | 60 | return $this->headers; |
|
103 | } |
||
104 | |||
105 | /** |
||
106 | * Creates an HTTP response |
||
107 | * |
||
108 | * @param string $content The response content |
||
109 | * @param integer $status The response status code |
||
110 | * @param array $headers An array of response headers |
||
111 | * @return Response |
||
112 | */ |
||
113 | 45 | View Code Duplication | public function prepareResponse($content = '', $status = 200, array $headers = []) { |
114 | 45 | $header_bag = $this->getHeaders(); |
|
115 | 45 | $header_bag->add($headers); |
|
116 | 45 | $response = new Response($content, $status, $header_bag->all()); |
|
117 | 45 | $response->prepare($this->request); |
|
118 | 45 | return $response; |
|
119 | } |
||
120 | |||
121 | /** |
||
122 | * Creates a redirect response |
||
123 | * |
||
124 | * @param string $url URL to redirect to |
||
125 | * @param integer $status The status code (302 by default) |
||
126 | * @param array $headers An array of response headers (Location is always set to the given URL) |
||
127 | * @return SymfonyRedirectResponse |
||
128 | * @throws InvalidArgumentException |
||
129 | */ |
||
130 | 14 | View Code Duplication | public function prepareRedirectResponse($url, $status = 302, array $headers = []) { |
131 | 14 | $header_bag = $this->getHeaders(); |
|
132 | 14 | $header_bag->add($headers); |
|
133 | 14 | $response = new SymfonyRedirectResponse($url, $status, $header_bag->all()); |
|
134 | 13 | $response->prepare($this->request); |
|
135 | 13 | return $response; |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * Send a response |
||
140 | * |
||
141 | * @param Response $response Response object |
||
142 | * @return Response |
||
143 | */ |
||
144 | 85 | public function send(Response $response) { |
|
145 | |||
146 | 85 | if ($this->response_sent) { |
|
147 | 35 | if ($this->response_sent !== $response) { |
|
148 | 4 | _elgg_services()->logger->error('Unable to send the following response: ' . PHP_EOL |
|
149 | 4 | . (string) $response . PHP_EOL |
|
150 | 4 | . 'because another response has already been sent: ' . PHP_EOL |
|
151 | 35 | . (string) $this->response_sent); |
|
152 | } |
||
153 | } else { |
||
154 | 85 | if (!elgg_trigger_before_event('send', 'http_response', $response)) { |
|
155 | return false; |
||
0 ignored issues
–
show
|
|||
156 | } |
||
157 | |||
158 | 85 | if (!$this->transport->send($response)) { |
|
159 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Elgg\Http\ResponseFactory::send of type Symfony\Component\HttpFoundation\Response .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function
Loading history...
|
|||
160 | } |
||
161 | |||
162 | 85 | elgg_trigger_after_event('send', 'http_response', $response); |
|
163 | 85 | $this->response_sent = $response; |
|
164 | } |
||
165 | |||
166 | 85 | return $this->response_sent; |
|
167 | } |
||
168 | |||
169 | /** |
||
170 | * Returns a response that was sent to the client |
||
171 | * @return Response|false |
||
172 | */ |
||
173 | 78 | public function getSentResponse() { |
|
174 | 78 | return $this->response_sent; |
|
175 | } |
||
176 | |||
177 | /** |
||
178 | * Send HTTP response |
||
179 | * |
||
180 | * @param ResponseBuilder $response ResponseBuilder instance |
||
181 | * An instance of an ErrorResponse, OkResponse or RedirectResponse |
||
182 | * @return Response |
||
183 | */ |
||
184 | 70 | public function respond(ResponseBuilder $response) { |
|
185 | |||
186 | 70 | $response_type = $this->parseContext(); |
|
187 | 70 | $response = $this->hooks->trigger('response', $response_type, $response, $response); |
|
188 | 70 | if (!$response instanceof ResponseBuilder) { |
|
189 | throw new InvalidParameterException("Handlers for 'response','$response_type' plugin hook must " |
||
190 | . "return an instanceof " . ResponseBuilder::class); |
||
191 | } |
||
192 | |||
193 | 70 | if ($response->isNotModified()) { |
|
194 | return $this->send($this->prepareResponse('', ELGG_HTTP_NOT_MODIFIED)); |
||
195 | } |
||
196 | |||
197 | 70 | $is_xhr = $this->request->isXmlHttpRequest(); |
|
198 | |||
199 | 70 | $is_action = false; |
|
200 | 70 | if (0 === strpos($response_type, 'action:')) { |
|
201 | 22 | $is_action = true; |
|
202 | } |
||
203 | |||
204 | 70 | if ($is_action && $response->getForwardURL() === null) { |
|
205 | // actions must always set a redirect url |
||
206 | $response->setForwardURL(REFERRER); |
||
207 | } |
||
208 | |||
209 | 70 | if ($response->getForwardURL() === REFERRER) { |
|
210 | 16 | $response->setForwardURL($this->request->headers->get('Referer')); |
|
211 | } |
||
212 | |||
213 | 70 | if ($response->getForwardURL() !== null && !$is_xhr) { |
|
214 | // non-xhr requests should issue a forward if redirect url is set |
||
215 | // unless it's an error, in which case we serve an error page |
||
216 | 10 | if ($this->isAction() || (!$response->isClientError() && !$response->isServerError())) { |
|
217 | 6 | $response->setStatusCode(ELGG_HTTP_FOUND); |
|
218 | } |
||
219 | } |
||
220 | |||
221 | 70 | if ($is_xhr && $is_action && !$this->ajax->isAjax2Request()) { |
|
222 | // xhr actions using legacy ajax API should return 200 with wrapped data |
||
223 | 6 | $response->setStatusCode(ELGG_HTTP_OK); |
|
224 | 6 | $response->setContent($this->wrapLegacyAjaxResponse($response->getContent(), $response->getForwardURL())); |
|
225 | } |
||
226 | |||
227 | 70 | if ($is_xhr && $is_action) { |
|
228 | // Actions always respond with JSON on xhr calls |
||
229 | 17 | $headers = $response->getHeaders(); |
|
230 | 17 | $headers['Content-Type'] = 'application/json; charset=UTF-8'; |
|
231 | 17 | $response->setHeaders($headers); |
|
232 | } |
||
233 | |||
234 | 70 | $content = $this->stringify($response->getContent()); |
|
235 | 70 | $status_code = $response->getStatusCode(); |
|
236 | 70 | $headers = $response->getHeaders(); |
|
237 | |||
238 | 70 | if ($response->isRedirection()) { |
|
239 | 9 | $redirect_url = $response->getForwardURL(); |
|
240 | 9 | return $this->redirect($redirect_url, $status_code); |
|
241 | } |
||
242 | |||
243 | 64 | if ($this->ajax->isReady() && $response->isSuccessful()) { |
|
244 | 20 | return $this->respondFromContent($content, $status_code, $headers); |
|
245 | } |
||
246 | |||
247 | 44 | if ($response->isClientError() || $response->isServerError() || $response instanceof ErrorResponse) { |
|
248 | 16 | return $this->respondWithError($content, $status_code, $headers); |
|
249 | } |
||
250 | |||
251 | 28 | return $this->respondFromContent($content, $status_code, $headers); |
|
252 | } |
||
253 | |||
254 | /** |
||
255 | * Send error HTTP response |
||
256 | * |
||
257 | * @param string $error Error message |
||
258 | * @param int $status_code HTTP status code |
||
259 | * @param array $headers HTTP headers (will be discarded on AJAX requests) |
||
260 | * @return Response |
||
261 | * @throws InvalidParameterException |
||
262 | */ |
||
263 | 16 | public function respondWithError($error, $status_code = ELGG_HTTP_BAD_REQUEST, array $headers = []) { |
|
264 | 16 | if ($this->ajax->isReady()) { |
|
265 | 7 | return $this->send($this->ajax->respondWithError($error, $status_code)); |
|
266 | } |
||
267 | |||
268 | 9 | if ($this->isXhr()) { |
|
269 | // xhr calls to non-actions (e.g. ajax/view or ajax/form) need to receive proper HTTP status code |
||
270 | 5 | return $this->send($this->prepareResponse($error, $status_code, $headers)); |
|
271 | } |
||
272 | |||
273 | 4 | $forward_url = $this->request->headers->get('Referer'); |
|
274 | |||
275 | 4 | if (!$this->isAction()) { |
|
276 | $params = [ |
||
277 | 4 | 'current_url' => current_page_url(), |
|
278 | 4 | 'forward_url' => $forward_url, |
|
279 | ]; |
||
280 | // For BC, let plugins serve their own error page |
||
281 | // @see elgg_error_page_handler |
||
282 | 4 | $forward_reason = (string) $status_code; |
|
283 | |||
284 | 4 | $forward_url = $this->hooks->trigger('forward', $forward_reason, $params, $forward_url); |
|
285 | |||
286 | 4 | if ($this->response_sent) { |
|
287 | // Response was sent from a forward hook |
||
288 | return $this->response_sent; |
||
289 | } |
||
290 | |||
291 | 4 | $params['type'] = $forward_reason; |
|
292 | 4 | $error_page = elgg_view_resource('error', $params); |
|
293 | 4 | return $this->send($this->prepareResponse($error_page, $status_code)); |
|
294 | } |
||
295 | |||
296 | $forward_url = elgg_normalize_url($forward_url); |
||
297 | return $this->send($this->prepareRedirectResponse($forward_url)); |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Send OK response |
||
302 | * |
||
303 | * @param string $content Response body |
||
304 | * @param int $status_code HTTP status code |
||
305 | * @param array $headers HTTP headers (will be discarded for AJAX requets) |
||
306 | * @return type |
||
307 | */ |
||
308 | 48 | public function respondFromContent($content = '', $status_code = ELGG_HTTP_OK, array $headers = []) { |
|
309 | |||
310 | 48 | if ($this->ajax->isReady()) { |
|
311 | 20 | $hook_type = $this->parseContext(); |
|
312 | // $this->ajax->setStatusCode($status_code); |
||
313 | 20 | return $this->send($this->ajax->respondFromOutput($content, $hook_type)); |
|
314 | } |
||
315 | |||
316 | 28 | return $this->send($this->prepareResponse($content, $status_code, $headers)); |
|
317 | } |
||
318 | |||
319 | /** |
||
320 | * Wraps content for compability with legacy Elgg ajax calls |
||
321 | * |
||
322 | * @param string $content Response content |
||
323 | * @param string $forward_url Forward URL |
||
324 | * @return string |
||
325 | */ |
||
326 | 11 | public function wrapLegacyAjaxResponse($content = '', $forward_url = REFERRER) { |
|
327 | |||
328 | 11 | $content = $this->stringify($content); |
|
329 | |||
330 | 11 | if ($forward_url === REFERRER) { |
|
331 | $forward_url = $this->request->headers->get('Referer'); |
||
332 | } |
||
333 | |||
334 | // always pass the full structure to avoid boilerplate JS code. |
||
335 | $params = [ |
||
336 | 11 | 'output' => '', |
|
337 | 11 | 'status' => 0, |
|
338 | 'system_messages' => [ |
||
339 | 'error' => [], |
||
340 | 'success' => [] |
||
341 | ], |
||
342 | 11 | 'current_url' => current_page_url(), |
|
343 | 11 | 'forward_url' => elgg_normalize_url($forward_url), |
|
344 | ]; |
||
345 | |||
346 | 11 | $params['output'] = $this->ajax->decodeJson($content); |
|
347 | |||
348 | // Grab any system messages so we can inject them via ajax too |
||
349 | 11 | $system_messages = _elgg_services()->systemMessages->dumpRegister(); |
|
350 | |||
351 | 11 | if (isset($system_messages['success'])) { |
|
352 | 2 | $params['system_messages']['success'] = $system_messages['success']; |
|
353 | } |
||
354 | |||
355 | 11 | if (isset($system_messages['error'])) { |
|
356 | 3 | $params['system_messages']['error'] = $system_messages['error']; |
|
357 | 3 | $params['status'] = -1; |
|
358 | } |
||
359 | |||
360 | 11 | $response_type = $this->parseContext(); |
|
361 | 11 | list($service, $name) = explode(':', $response_type); |
|
362 | $context = [ |
||
363 | 11 | $service => $name, |
|
364 | ]; |
||
365 | 11 | $params = $this->hooks->trigger('output', 'ajax', $context, $params); |
|
366 | |||
367 | 11 | return $this->stringify($params); |
|
368 | } |
||
369 | |||
370 | /** |
||
371 | * Prepares a redirect response |
||
372 | * |
||
373 | * @param string $forward_url Redirection URL |
||
374 | * @param mixed $status_code HTTP status code or forward reason |
||
375 | * @return SymfonyRedirectResponse |
||
376 | * @throws InvalidParameterException |
||
377 | */ |
||
378 | 26 | public function redirect($forward_url = REFERRER, $status_code = ELGG_HTTP_FOUND) { |
|
379 | |||
380 | 26 | if ($forward_url === REFERRER) { |
|
381 | 5 | $forward_url = $this->request->headers->get('Referer'); |
|
382 | } |
||
383 | |||
384 | 26 | $forward_url = elgg_normalize_url($forward_url); |
|
385 | |||
386 | // allow plugins to rewrite redirection URL |
||
387 | 26 | $current_page = current_page_url(); |
|
388 | $params = [ |
||
389 | 26 | 'current_url' => $current_page, |
|
390 | 26 | 'forward_url' => $forward_url |
|
391 | ]; |
||
392 | |||
393 | 26 | $forward_reason = (string) $status_code; |
|
394 | |||
395 | 26 | $forward_url = $this->hooks->trigger('forward', $forward_reason, $params, $forward_url); |
|
396 | |||
397 | 26 | if ($this->response_sent) { |
|
398 | // Response was sent from a forward hook |
||
399 | // Clearing handlers to void infinite loops |
||
400 | 1 | return $this->response_sent; |
|
401 | } |
||
402 | |||
403 | 26 | if ($forward_url === REFERRER) { |
|
404 | $forward_url = $this->request->headers->get('Referer'); |
||
405 | } |
||
406 | |||
407 | 26 | if (!is_string($forward_url)) { |
|
408 | throw new InvalidParameterException("'forward', '$forward_reason' hook must return a valid redirection URL"); |
||
409 | } |
||
410 | |||
411 | 26 | $forward_url = elgg_normalize_url($forward_url); |
|
412 | |||
413 | switch ($status_code) { |
||
414 | 26 | case 'system': |
|
415 | 26 | case 'csrf': |
|
416 | 2 | $status_code = ELGG_HTTP_OK; |
|
417 | 2 | break; |
|
418 | 24 | case 'admin': |
|
419 | 24 | case 'login': |
|
420 | 24 | case 'member': |
|
421 | 24 | case 'walled_garden': |
|
422 | default : |
||
423 | 24 | $status_code = (int) $status_code; |
|
424 | 24 | if (!$status_code || $status_code < 100 || $status_code > 599) { |
|
425 | 1 | $status_code = ELGG_HTTP_SEE_OTHER; |
|
426 | } |
||
427 | 24 | break; |
|
428 | } |
||
429 | |||
430 | 26 | if ($this->isXhr()) { |
|
431 | 12 | View Code Duplication | if ($status_code < 100 || ($status_code >= 300 && $status_code <= 399) || $status_code > 599) { |
432 | // We only want to preserve OK and error codes |
||
433 | // Redirect responses should be converted to OK responses as this is an XHR request |
||
434 | 9 | $status_code = ELGG_HTTP_OK; |
|
435 | } |
||
436 | 12 | $output = ob_get_clean(); |
|
437 | 12 | if (!$this->isAction() && !$this->ajax->isAjax2Request()) { |
|
438 | // legacy ajax calls are always OK |
||
439 | // actions are wrapped by ResponseFactory::respond() |
||
440 | 5 | $status_code = ELGG_HTTP_OK; |
|
441 | 5 | $output = $this->wrapLegacyAjaxResponse($output, $forward_url); |
|
442 | } |
||
443 | |||
444 | 12 | $response = new OkResponse($output, $status_code, $forward_url); |
|
445 | 12 | $headers = $response->getHeaders(); |
|
446 | 12 | $headers['Content-Type'] = 'application/json; charset=UTF-8'; |
|
447 | 12 | $response->setHeaders($headers); |
|
448 | 12 | return $this->respond($response); |
|
449 | } |
||
450 | |||
451 | 14 | if ($this->isAction()) { |
|
452 | // actions should always redirect on non xhr-calls |
||
453 | 8 | if (!is_int($status_code) || $status_code < 300 || $status_code > 399) { |
|
454 | 2 | $status_code = ELGG_HTTP_SEE_OTHER; |
|
455 | } |
||
456 | } |
||
457 | |||
458 | 14 | $response = new OkResponse('', $status_code, $forward_url); |
|
459 | 14 | if ($response->isRedirection()) { |
|
460 | 12 | return $this->send($this->prepareRedirectResponse($forward_url, $status_code)); |
|
461 | } |
||
462 | 2 | return $this->respond($response); |
|
463 | } |
||
464 | |||
465 | /** |
||
466 | * Parses response type to be used as plugin hook type |
||
467 | * @return string |
||
468 | */ |
||
469 | 85 | public function parseContext() { |
|
470 | |||
471 | 85 | $segments = $this->request->getUrlSegments(); |
|
472 | |||
473 | 85 | $identifier = array_shift($segments); |
|
474 | switch ($identifier) { |
||
475 | 85 | case 'ajax' : |
|
476 | 21 | $page = array_shift($segments); |
|
477 | 21 | if ($page === 'view') { |
|
478 | 14 | $view = implode('/', $segments); |
|
479 | 14 | return "view:$view"; |
|
480 | 7 | } else if ($page === 'form') { |
|
481 | 5 | $form = implode('/', $segments); |
|
482 | 5 | return "form:$form"; |
|
483 | } |
||
484 | 2 | array_unshift($segments, $page); |
|
485 | 2 | break; |
|
486 | |||
487 | 64 | case 'action' : |
|
488 | 28 | $action = implode('/', $segments); |
|
489 | 28 | return "action:$action"; |
|
490 | } |
||
491 | |||
492 | 39 | array_unshift($segments, $identifier); |
|
493 | 39 | $path = implode('/', $segments); |
|
494 | 39 | return "path:$path"; |
|
495 | } |
||
496 | |||
497 | /** |
||
498 | * Check if the request is an XmlHttpRequest |
||
499 | * @return bool |
||
500 | */ |
||
501 | 35 | public function isXhr() { |
|
502 | 35 | return $this->request->isXmlHttpRequest(); |
|
503 | } |
||
504 | |||
505 | /** |
||
506 | * Check if the requested path is an action |
||
507 | * @return bool |
||
508 | */ |
||
509 | 29 | public function isAction() { |
|
510 | 29 | if (0 === strpos($this->parseContext(), 'action:')) { |
|
511 | 12 | return true; |
|
512 | } |
||
513 | 18 | return false; |
|
514 | } |
||
515 | |||
516 | /** |
||
517 | * Normalizes content into serializable data by walking through arrays |
||
518 | * and objectifying Elgg entities |
||
519 | * |
||
520 | * @param mixed $content Data to normalize |
||
521 | * @return mixed |
||
522 | */ |
||
523 | 70 | public function normalize($content = '') { |
|
524 | 70 | if ($content instanceof ElggEntity) { |
|
525 | $content = (array) $content->toObject(); |
||
526 | } |
||
527 | 70 | if (is_array($content)) { |
|
528 | 14 | foreach ($content as $key => $value) { |
|
529 | 14 | $content[$key] = $this->normalize($value); |
|
530 | } |
||
531 | } |
||
532 | 70 | return $content; |
|
533 | } |
||
534 | |||
535 | /** |
||
536 | * Stringify/serialize response data |
||
537 | * |
||
538 | * Casts objects implementing __toString method to strings |
||
539 | * Serializes non-scalar values to JSON |
||
540 | * |
||
541 | * @param mixed $content Content to serialize |
||
542 | * @return string |
||
543 | */ |
||
544 | 70 | public function stringify($content = '') { |
|
545 | 70 | $content = $this->normalize($content); |
|
546 | 70 | if (empty($content) || (is_object($content) && is_callable($content, '__toString'))) { |
|
547 | 21 | return (string) $content; |
|
548 | } |
||
549 | 54 | if (is_scalar($content)) { |
|
550 | 51 | return $content; |
|
551 | } |
||
552 | 14 | return json_encode($content, ELGG_JSON_ENCODING); |
|
553 | } |
||
554 | |||
555 | } |
||
556 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.