1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the FOSRestBundle package. |
5
|
|
|
* |
6
|
|
|
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace FOS\RestBundle\View; |
13
|
|
|
|
14
|
|
|
use FOS\RestBundle\Context\Context; |
15
|
|
|
use FOS\RestBundle\Serializer\Serializer; |
16
|
|
|
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; |
17
|
|
|
use Symfony\Component\Form\FormInterface; |
18
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
19
|
|
|
use Symfony\Component\HttpFoundation\Request; |
20
|
|
|
use Symfony\Component\HttpFoundation\RequestStack; |
21
|
|
|
use Symfony\Component\HttpFoundation\Response; |
22
|
|
|
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; |
23
|
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
24
|
|
|
use Symfony\Component\Templating\TemplateReferenceInterface; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* View may be used in controllers to build up a response in a format agnostic way |
28
|
|
|
* The View class takes care of encoding your data in json, xml, or renders a |
29
|
|
|
* template for html via the Serializer component. |
30
|
|
|
* |
31
|
|
|
* @author Jordi Boggiano <[email protected]> |
32
|
|
|
* @author Lukas K. Smith <[email protected]> |
33
|
|
|
*/ |
34
|
|
|
class ViewHandler implements ConfigurableViewHandlerInterface |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* Key format, value a callable that returns a Response instance. |
38
|
|
|
* |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
protected $customHandlers = []; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* The supported formats as keys and if the given formats |
45
|
|
|
* uses templating is denoted by a true value. |
46
|
|
|
* |
47
|
|
|
* @var array |
48
|
|
|
*/ |
49
|
|
|
protected $formats; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* HTTP response status code for a failed validation. |
53
|
|
|
* |
54
|
|
|
* @var int |
55
|
|
|
*/ |
56
|
|
|
protected $failedValidationCode; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* HTTP response status code when the view data is null. |
60
|
|
|
* |
61
|
|
|
* @var int |
62
|
|
|
*/ |
63
|
|
|
protected $emptyContentCode; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Whether or not to serialize null view data. |
67
|
|
|
* |
68
|
|
|
* @var bool |
69
|
|
|
*/ |
70
|
|
|
protected $serializeNull; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* If to force a redirect for the given key format, |
74
|
|
|
* with value being the status code to use. |
75
|
|
|
* |
76
|
|
|
* @var array |
77
|
|
|
*/ |
78
|
|
|
protected $forceRedirects; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @var string |
82
|
|
|
*/ |
83
|
|
|
protected $defaultEngine; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var array |
87
|
|
|
*/ |
88
|
|
|
protected $exclusionStrategyGroups = []; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @var string |
92
|
|
|
*/ |
93
|
|
|
protected $exclusionStrategyVersion; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @var bool |
97
|
|
|
*/ |
98
|
|
|
protected $serializeNullStrategy; |
99
|
|
|
|
100
|
|
|
private $urlGenerator; |
101
|
|
|
private $serializer; |
102
|
|
|
private $templating; |
103
|
|
|
private $requestStack; |
104
|
|
|
|
105
|
|
|
private $options; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Constructor. |
109
|
|
|
* |
110
|
|
|
* @param UrlGeneratorInterface $urlGenerator The URL generator |
111
|
|
|
* @param Serializer $serializer |
112
|
|
|
* @param EngineInterface $templating The configured templating engine |
113
|
|
|
* @param RequestStack $requestStack The request stack |
114
|
|
|
* @param array $formats the supported formats as keys and if the given formats uses templating is denoted by a true value |
115
|
|
|
* @param int $failedValidationCode The HTTP response status code for a failed validation |
116
|
|
|
* @param int $emptyContentCode HTTP response status code when the view data is null |
117
|
|
|
* @param bool $serializeNull Whether or not to serialize null view data |
118
|
|
|
* @param array $forceRedirects If to force a redirect for the given key format, with value being the status code to use |
119
|
81 |
|
* @param string $defaultEngine default engine (twig, php ..) |
120
|
|
|
* @param array $options config options |
121
|
|
|
*/ |
122
|
|
|
public function __construct( |
123
|
|
|
UrlGeneratorInterface $urlGenerator, |
124
|
|
|
Serializer $serializer, |
125
|
|
|
EngineInterface $templating = null, |
126
|
|
|
RequestStack $requestStack, |
127
|
|
|
array $formats = null, |
128
|
|
|
$failedValidationCode = Response::HTTP_BAD_REQUEST, |
129
|
|
|
$emptyContentCode = Response::HTTP_NO_CONTENT, |
130
|
|
|
$serializeNull = false, |
131
|
81 |
|
array $forceRedirects = null, |
132
|
81 |
|
$defaultEngine = 'twig', |
133
|
81 |
|
array $options = [] |
134
|
81 |
|
) { |
135
|
81 |
|
$this->urlGenerator = $urlGenerator; |
136
|
81 |
|
$this->serializer = $serializer; |
137
|
81 |
|
$this->templating = $templating; |
138
|
81 |
|
$this->requestStack = $requestStack; |
139
|
81 |
|
$this->formats = (array) $formats; |
140
|
81 |
|
$this->failedValidationCode = $failedValidationCode; |
141
|
81 |
|
$this->emptyContentCode = $emptyContentCode; |
142
|
|
|
$this->serializeNull = $serializeNull; |
143
|
|
|
$this->forceRedirects = (array) $forceRedirects; |
144
|
|
|
$this->defaultEngine = $defaultEngine; |
145
|
|
|
$this->options = $options + [ |
146
|
|
|
'exclusionStrategyGroups' => [], |
147
|
|
|
'exclusionStrategyVersion' => null, |
148
|
1 |
|
'serializeNullStrategy' => null, |
149
|
|
|
]; |
150
|
1 |
|
$this->reset(); |
151
|
1 |
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Sets the default serialization groups. |
155
|
|
|
* |
156
|
|
|
* @param array|string $groups |
157
|
|
|
*/ |
158
|
7 |
|
public function setExclusionStrategyGroups($groups) |
159
|
|
|
{ |
160
|
7 |
|
$this->exclusionStrategyGroups = (array) $groups; |
161
|
7 |
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Sets the default serialization version. |
165
|
|
|
* |
166
|
|
|
* @param string $version |
167
|
|
|
*/ |
168
|
25 |
|
public function setExclusionStrategyVersion($version) |
169
|
|
|
{ |
170
|
25 |
|
$this->exclusionStrategyVersion = $version; |
171
|
25 |
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* If nulls should be serialized. |
175
|
|
|
* |
176
|
44 |
|
* @param bool $isEnabled |
177
|
|
|
*/ |
178
|
44 |
|
public function setSerializeNullStrategy($isEnabled) |
179
|
|
|
{ |
180
|
|
|
$this->serializeNullStrategy = $isEnabled; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* {@inheritdoc} |
185
|
|
|
*/ |
186
|
|
|
public function supports($format) |
187
|
|
|
{ |
188
|
|
|
return isset($this->customHandlers[$format]) || isset($this->formats[$format]); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Registers a custom handler. |
193
|
16 |
|
* |
194
|
|
|
* The handler must have the following signature: handler(ViewHandler $viewHandler, View $view, Request $request, $format) |
195
|
16 |
|
* It can use the public methods of this class to retrieve the needed data and return a |
196
|
1 |
|
* Response object ready to be sent. |
197
|
|
|
* |
198
|
|
|
* @param string $format |
199
|
15 |
|
* @param callable $callable |
200
|
15 |
|
* |
201
|
|
|
* @throws \InvalidArgumentException |
202
|
|
|
*/ |
203
|
|
|
public function registerHandler($format, $callable) |
204
|
|
|
{ |
205
|
|
|
if (!is_callable($callable)) { |
206
|
|
|
throw new \InvalidArgumentException('Registered view callback must be callable.'); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
$this->customHandlers[$format] = $callable; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Gets a response HTTP status code from a View instance. |
214
|
55 |
|
* |
215
|
|
|
* By default it will return 200. However if there is a FormInterface stored for |
216
|
55 |
|
* the key 'form' in the View's data it will return the failed_validation |
217
|
|
|
* configuration if the form instance has errors. |
218
|
55 |
|
* |
219
|
7 |
|
* @param View $view |
220
|
|
|
* @param mixed $content |
221
|
|
|
* |
222
|
48 |
|
* @return int HTTP status code |
223
|
48 |
|
*/ |
224
|
15 |
|
protected function getStatusCode(View $view, $content = null) |
225
|
|
|
{ |
226
|
|
|
$form = $this->getFormFromView($view); |
227
|
33 |
|
|
228
|
|
|
if ($form && $form->isSubmitted() && !$form->isValid()) { |
229
|
|
|
return $this->failedValidationCode; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$statusCode = $view->getStatusCode(); |
233
|
|
|
if (null !== $statusCode) { |
234
|
|
|
return $statusCode; |
235
|
|
|
} |
236
|
|
|
|
237
|
46 |
|
return null !== $content ? Response::HTTP_OK : $this->emptyContentCode; |
238
|
|
|
} |
239
|
46 |
|
|
240
|
|
|
/** |
241
|
|
|
* If the given format uses the templating system for rendering. |
242
|
|
|
* |
243
|
|
|
* @param string $format |
244
|
|
|
* |
245
|
|
|
* @return bool |
246
|
|
|
*/ |
247
|
|
|
public function isFormatTemplating($format) |
248
|
|
|
{ |
249
|
|
|
return !empty($this->formats[$format]); |
250
|
31 |
|
} |
251
|
|
|
|
252
|
31 |
|
/** |
253
|
|
|
* Gets or creates a JMS\Serializer\SerializationContext and initializes it with |
254
|
31 |
|
* the view exclusion strategies, groups & versions if a new context is created. |
255
|
31 |
|
* |
256
|
1 |
|
* @param View $view |
257
|
1 |
|
* |
258
|
|
|
* @return Context |
259
|
31 |
|
*/ |
260
|
5 |
|
protected function getSerializationContext(View $view) |
261
|
5 |
|
{ |
262
|
|
|
$context = $view->getContext(); |
263
|
31 |
|
|
264
|
18 |
|
$groups = $context->getGroups(); |
265
|
18 |
|
if (empty($groups) && $this->exclusionStrategyGroups) { |
|
|
|
|
266
|
|
|
$context->setGroups($this->exclusionStrategyGroups); |
267
|
31 |
|
} |
268
|
|
|
|
269
|
|
|
if (null === $context->getVersion() && $this->exclusionStrategyVersion) { |
270
|
|
|
$context->setVersion($this->exclusionStrategyVersion); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
if (null === $context->getSerializeNull() && null !== $this->serializeNullStrategy) { |
274
|
|
|
$context->setSerializeNull($this->serializeNullStrategy); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return $context; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Handles a request with the proper handler. |
282
|
40 |
|
* |
283
|
|
|
* Decides on which handler to use based on the request format. |
284
|
40 |
|
* |
285
|
12 |
|
* @param View $view |
286
|
12 |
|
* @param Request $request |
287
|
|
|
* |
288
|
40 |
|
* @throws UnsupportedMediaTypeHttpException |
289
|
|
|
* |
290
|
40 |
|
* @return Response |
291
|
1 |
|
*/ |
292
|
1 |
|
public function handle(View $view, Request $request = null) |
293
|
|
|
{ |
294
|
|
|
if (null === $request) { |
295
|
39 |
|
$request = $this->requestStack->getCurrentRequest(); |
296
|
10 |
|
} |
297
|
|
|
|
298
|
|
|
$format = $view->getFormat() ?: $request->getRequestFormat(); |
|
|
|
|
299
|
29 |
|
|
300
|
|
|
if (!$this->supports($format)) { |
301
|
|
|
$msg = "Format '$format' not supported, handler must be implemented"; |
302
|
|
|
|
303
|
|
|
throw new UnsupportedMediaTypeHttpException($msg); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
if (isset($this->customHandlers[$format])) { |
307
|
|
|
return call_user_func($this->customHandlers[$format], $this, $view, $request, $format); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return $this->createResponse($view, $request, $format); |
|
|
|
|
311
|
8 |
|
} |
312
|
|
|
|
313
|
8 |
|
/** |
314
|
8 |
|
* Creates the Response from the view. |
315
|
1 |
|
* |
316
|
1 |
|
* @param View $view |
317
|
7 |
|
* @param string $location |
318
|
7 |
|
* @param string $format |
319
|
2 |
|
* |
320
|
2 |
|
* @return Response |
321
|
2 |
|
*/ |
322
|
2 |
|
public function createRedirectResponse(View $view, $location, $format) |
323
|
|
|
{ |
324
|
|
|
$content = null; |
325
|
8 |
|
if ((Response::HTTP_CREATED === $view->getStatusCode() || Response::HTTP_ACCEPTED === $view->getStatusCode()) && null !== $view->getData()) { |
326
|
8 |
|
$response = $this->initResponse($view, $format); |
327
|
|
|
} else { |
328
|
8 |
|
$response = $view->getResponse(); |
329
|
8 |
|
if ('html' === $format && isset($this->forceRedirects[$format])) { |
330
|
|
|
$redirect = new RedirectResponse($location); |
331
|
8 |
|
$content = $redirect->getContent(); |
332
|
|
|
$response->setContent($content); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
$code = isset($this->forceRedirects[$format]) |
337
|
|
|
? $this->forceRedirects[$format] : $this->getStatusCode($view, $content); |
338
|
|
|
|
339
|
|
|
$response->setStatusCode($code); |
340
|
|
|
$response->headers->set('Location', $location); |
341
|
|
|
|
342
|
15 |
|
return $response; |
343
|
|
|
} |
344
|
15 |
|
|
345
|
|
|
/** |
346
|
15 |
|
* Renders the view data with the given template. |
347
|
15 |
|
* |
348
|
2 |
|
* @param View $view |
349
|
|
|
* @param string $format |
350
|
|
|
* |
351
|
|
|
* @return string |
352
|
2 |
|
*/ |
353
|
|
|
public function renderTemplate(View $view, $format) |
354
|
|
|
{ |
355
|
|
|
if (null === $this->templating) { |
356
|
2 |
|
throw new \LogicException(sprintf('An instance of %s must be injected in %s to render templates.', EngineInterface::class, __CLASS__)); |
357
|
|
|
} |
358
|
15 |
|
|
359
|
|
|
$data = $this->prepareTemplateParameters($view); |
360
|
|
|
|
361
|
|
|
$template = $view->getTemplate(); |
362
|
|
|
if ($template instanceof TemplateReferenceInterface) { |
363
|
|
|
if (null === $template->get('format')) { |
364
|
|
|
$template->set('format', $format); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
if (null === $template->get('engine')) { |
368
|
22 |
|
$engine = $view->getEngine() ?: $this->defaultEngine; |
369
|
|
|
$template->set('engine', $engine); |
370
|
22 |
|
} |
371
|
|
|
} |
372
|
22 |
|
|
373
|
2 |
|
return $this->templating->render($template, $data); |
|
|
|
|
374
|
22 |
|
} |
375
|
12 |
|
|
376
|
12 |
|
/** |
377
|
|
|
* Prepares view data for use by templating engine. |
378
|
22 |
|
* |
379
|
2 |
|
* @param View $view |
380
|
2 |
|
* |
381
|
|
|
* @return array |
382
|
22 |
|
*/ |
383
|
22 |
|
public function prepareTemplateParameters(View $view) |
384
|
2 |
|
{ |
385
|
2 |
|
$data = $view->getData(); |
386
|
|
|
|
387
|
22 |
|
if ($data instanceof FormInterface) { |
388
|
|
|
$data = [$view->getTemplateVar() => $data->getData(), 'form' => $data]; |
389
|
|
|
} elseif (empty($data) || !is_array($data) || is_numeric((key($data)))) { |
390
|
|
|
$data = [$view->getTemplateVar() => $data]; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
if (isset($data['form']) && $data['form'] instanceof FormInterface) { |
394
|
|
|
$data['form'] = $data['form']->createView(); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
$templateData = $view->getTemplateData(); |
398
|
|
|
if (is_callable($templateData)) { |
399
|
51 |
|
$templateData = call_user_func($templateData, $this, $view); |
400
|
|
|
} |
401
|
51 |
|
|
402
|
|
|
return array_merge($data, $templateData); |
403
|
|
|
} |
404
|
51 |
|
|
405
|
51 |
|
/** |
406
|
|
|
* Handles creation of a Response using either redirection or the templating/serializer service. |
407
|
51 |
|
* |
408
|
8 |
|
* @param View $view |
409
|
|
|
* @param Request $request |
410
|
|
|
* @param string $format |
411
|
43 |
|
* |
412
|
|
|
* @return Response |
413
|
43 |
|
*/ |
414
|
43 |
|
public function createResponse(View $view, Request $request, $format) |
415
|
43 |
|
{ |
416
|
|
|
$route = $view->getRoute(); |
417
|
43 |
|
|
418
|
|
|
$location = $route |
419
|
|
|
? $this->urlGenerator->generate($route, (array) $view->getRouteParameters(), UrlGeneratorInterface::ABSOLUTE_URL) |
420
|
|
|
: $view->getLocation(); |
421
|
|
|
|
422
|
|
|
if ($location) { |
|
|
|
|
423
|
|
|
return $this->createRedirectResponse($view, $location, $format); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
$response = $this->initResponse($view, $format); |
427
|
|
|
|
428
|
44 |
|
if (!$response->headers->has('Content-Type')) { |
429
|
|
|
$mimeType = $request->attributes->get('media_type'); |
430
|
44 |
|
if (null === $mimeType) { |
431
|
44 |
|
$mimeType = $request->getMimeType($format); |
432
|
15 |
|
} |
433
|
44 |
|
|
434
|
28 |
|
$response->headers->set('Content-Type', $mimeType); |
435
|
|
|
} |
436
|
28 |
|
|
437
|
6 |
|
return $response; |
438
|
6 |
|
} |
439
|
|
|
|
440
|
28 |
|
/** |
441
|
28 |
|
* Initializes a response object that represents the view and holds the view's status code. |
442
|
|
|
* |
443
|
28 |
|
* @param View $view |
444
|
28 |
|
* @param string $format |
445
|
|
|
* |
446
|
44 |
|
* @return Response |
447
|
44 |
|
*/ |
448
|
|
|
private function initResponse(View $view, $format) |
449
|
44 |
|
{ |
450
|
39 |
|
$content = null; |
451
|
39 |
|
if ($this->isFormatTemplating($format)) { |
452
|
|
|
$content = $this->renderTemplate($view, $format); |
453
|
44 |
|
} elseif ($this->serializeNull || null !== $view->getData()) { |
454
|
|
|
$data = $this->getDataFromView($view); |
455
|
|
|
|
456
|
|
|
if ($data instanceof FormInterface && $data->isSubmitted() && !$data->isValid()) { |
457
|
|
|
$view->getContext()->setAttribute('status_code', $this->failedValidationCode); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
$context = $this->getSerializationContext($view); |
461
|
|
|
$context->setAttribute('template_data', $view->getTemplateData()); |
462
|
|
|
|
463
|
55 |
|
$content = $this->serializer->serialize($data, $format, $context); |
464
|
|
|
} |
465
|
55 |
|
|
466
|
|
|
$response = $view->getResponse(); |
467
|
55 |
|
$response->setStatusCode($this->getStatusCode($view, $content)); |
468
|
7 |
|
|
469
|
|
|
if (null !== $content) { |
470
|
|
|
$response->setContent($content); |
471
|
48 |
|
} |
472
|
4 |
|
|
473
|
|
|
return $response; |
474
|
|
|
} |
475
|
44 |
|
|
476
|
|
|
/** |
477
|
|
|
* Returns the form from the given view if present, false otherwise. |
478
|
|
|
* |
479
|
|
|
* @param View $view |
480
|
|
|
* |
481
|
|
|
* @return bool|FormInterface |
482
|
|
|
*/ |
483
|
|
|
protected function getFormFromView(View $view) |
484
|
|
|
{ |
485
|
28 |
|
$data = $view->getData(); |
486
|
|
|
|
487
|
28 |
|
if ($data instanceof FormInterface) { |
488
|
|
|
return $data; |
489
|
28 |
|
} |
490
|
22 |
|
|
491
|
|
|
if (is_array($data) && isset($data['form']) && $data['form'] instanceof FormInterface) { |
492
|
|
|
return $data['form']; |
493
|
6 |
|
} |
494
|
|
|
|
495
|
|
|
return false; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* Returns the data from a view. |
500
|
|
|
* |
501
|
|
|
* @param View $view |
502
|
|
|
* |
503
|
|
|
* @return mixed|null |
504
|
|
|
*/ |
505
|
|
|
private function getDataFromView(View $view) |
506
|
|
|
{ |
507
|
|
|
$form = $this->getFormFromView($view); |
508
|
|
|
|
509
|
|
|
if (false === $form) { |
510
|
|
|
return $view->getData(); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
return $form; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Resets internal object state at the end of the request. |
518
|
|
|
*/ |
519
|
|
|
public function reset() |
520
|
|
|
{ |
521
|
|
|
$this->exclusionStrategyGroups = $this->options['exclusionStrategyGroups']; |
522
|
|
|
$this->exclusionStrategyVersion = $this->options['exclusionStrategyVersion']; |
523
|
|
|
$this->serializeNullStrategy = $this->options['serializeNullStrategy']; |
524
|
|
|
} |
525
|
|
|
} |
526
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.