Completed
Pull Request — 3.x (#6263)
by
unknown
53:39
created

ControllerTrait::json()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Controller;
15
16
use Doctrine\Persistence\ManagerRegistry;
17
use Psr\Container\ContainerInterface;
18
use Psr\Link\LinkInterface;
19
use Symfony\Component\Form\Extension\Core\Type\FormType;
20
use Symfony\Component\Form\FormBuilderInterface;
21
use Symfony\Component\Form\FormInterface;
22
use Symfony\Component\HttpFoundation\BinaryFileResponse;
23
use Symfony\Component\HttpFoundation\JsonResponse;
24
use Symfony\Component\HttpFoundation\RedirectResponse;
25
use Symfony\Component\HttpFoundation\Request;
26
use Symfony\Component\HttpFoundation\Response;
27
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
28
use Symfony\Component\HttpFoundation\StreamedResponse;
29
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
30
use Symfony\Component\HttpKernel\HttpKernelInterface;
31
use Symfony\Component\Messenger\Envelope;
32
use Symfony\Component\Messenger\Stamp\StampInterface;
33
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
34
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
35
use Symfony\Component\Security\Core\User\UserInterface;
36
use Symfony\Component\Security\Csrf\CsrfToken;
37
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
38
use Symfony\Component\WebLink\GenericLinkProvider;
39
40
/**
41
 * NEXT_MAJOR: Remove this trait.
42
 *
43
 * @see https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
44
 *
45
 * @internal
46
 *
47
 * @property ContainerInterface $container
48
 */
49
trait ControllerTrait
50
{
51
    /**
52
     * Returns true if the service id is defined.
53
     *
54
     * @final
55
     */
56
    protected function has(string $id): bool
57
    {
58
        return $this->container->has($id);
59
    }
60
61
    /**
62
     * Gets a container service by its id.
63
     *
64
     * @return object The service
65
     *
66
     * @final
67
     */
68
    protected function get(string $id)
69
    {
70
        return $this->container->get($id);
71
    }
72
73
    /**
74
     * Generates a URL from the given parameters.
75
     *
76
     * @see UrlGeneratorInterface
77
     *
78
     * @final
79
     */
80
    protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
81
    {
82
        return $this->container->get('router')->generate($route, $parameters, $referenceType);
83
    }
84
85
    /**
86
     * Forwards the request to another controller.
87
     *
88
     * @param string $controller The controller name (a string like Bundle\BlogBundle\Controller\PostController::indexAction)
89
     *
90
     * @final
91
     */
92
    protected function forward(string $controller, array $path = [], array $query = []): Response
93
    {
94
        $request = $this->container->get('request_stack')->getCurrentRequest();
95
        $path['_controller'] = $controller;
96
        $subRequest = $request->duplicate($query, null, $path);
97
98
        return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
99
    }
100
101
    /**
102
     * Returns a RedirectResponse to the given URL.
103
     *
104
     * @final
105
     */
106
    protected function redirect(string $url, int $status = 302): RedirectResponse
107
    {
108
        return new RedirectResponse($url, $status);
109
    }
110
111
    /**
112
     * Returns a RedirectResponse to the given route with the given parameters.
113
     *
114
     * @final
115
     */
116
    protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse
117
    {
118
        return $this->redirect($this->generateUrl($route, $parameters), $status);
119
    }
120
121
    /**
122
     * Returns a JsonResponse that uses the serializer component if enabled, or json_encode.
123
     *
124
     * @final
125
     */
126
    protected function json($data, int $status = 200, array $headers = [], array $context = []): JsonResponse
127
    {
128
        if ($this->container->has('serializer')) {
129
            $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([
130
                'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
131
            ], $context));
132
133
            return new JsonResponse($json, $status, $headers, true);
134
        }
135
136
        return new JsonResponse($data, $status, $headers);
137
    }
138
139
    /**
140
     * Returns a BinaryFileResponse object with original or customized file name and disposition header.
141
     *
142
     * @param \SplFileInfo|string $file File object or path to file to be sent as response
143
     *
144
     * @final
145
     */
146
    protected function file($file, ?string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse
147
    {
148
        $response = new BinaryFileResponse($file);
149
        $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName);
150
151
        return $response;
152
    }
153
154
    /**
155
     * Adds a flash message to the current session for type.
156
     *
157
     * @throws \LogicException
158
     *
159
     * @final
160
     */
161
    protected function addFlash(string $type, $message)
162
    {
163
        if (!$this->container->has('session')) {
164
            throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".');
165
        }
166
167
        $this->container->get('session')->getFlashBag()->add($type, $message);
168
    }
169
170
    /**
171
     * Checks if the attributes are granted against the current authentication token and optionally supplied subject.
172
     *
173
     * @throws \LogicException
174
     *
175
     * @final
176
     */
177
    protected function isGranted($attributes, $subject = null): bool
178
    {
179
        if (!$this->container->has('security.authorization_checker')) {
180
            throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
181
        }
182
183
        return $this->container->get('security.authorization_checker')->isGranted($attributes, $subject);
184
    }
185
186
    /**
187
     * Throws an exception unless the attributes are granted against the current authentication token and optionally
188
     * supplied subject.
189
     *
190
     * @throws AccessDeniedException
191
     *
192
     * @final
193
     */
194
    protected function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.')
195
    {
196
        if (!$this->isGranted($attributes, $subject)) {
197
            $exception = $this->createAccessDeniedException($message);
198
            $exception->setAttributes($attributes);
199
            $exception->setSubject($subject);
200
201
            throw $exception;
202
        }
203
    }
204
205
    /**
206
     * Returns a rendered view.
207
     *
208
     * @final
209
     */
210
    protected function renderView(string $view, array $parameters = []): string
211
    {
212
        if ($this->container->has('templating')) {
213
            @trigger_error('Using the "templating" service is deprecated since version 4.3 and will be removed in 5.0; use Twig instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
214
215
            return $this->container->get('templating')->render($view, $parameters);
216
        }
217
218
        if (!$this->container->has('twig')) {
219
            throw new \LogicException('You can not use the "renderView" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
220
        }
221
222
        return $this->container->get('twig')->render($view, $parameters);
223
    }
224
225
    /**
226
     * Renders a view.
227
     *
228
     * @final
229
     */
230
    protected function render(string $view, array $parameters = [], ?Response $response = null): Response
231
    {
232
        if ($this->container->has('templating')) {
233
            @trigger_error('Using the "templating" service is deprecated since version 4.3 and will be removed in 5.0; use Twig instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
234
235
            $content = $this->container->get('templating')->render($view, $parameters);
236
        } elseif ($this->container->has('twig')) {
237
            $content = $this->container->get('twig')->render($view, $parameters);
238
        } else {
239
            throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
240
        }
241
242
        if (null === $response) {
243
            $response = new Response();
244
        }
245
246
        $response->setContent($content);
247
248
        return $response;
249
    }
250
251
    /**
252
     * Streams a view.
253
     *
254
     * @final
255
     */
256
    protected function stream(string $view, array $parameters = [], ?StreamedResponse $response = null): StreamedResponse
257
    {
258
        if ($this->container->has('templating')) {
259
            @trigger_error('Using the "templating" service is deprecated since version 4.3 and will be removed in 5.0; use Twig instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
260
261
            $templating = $this->container->get('templating');
262
263
            $callback = static function () use ($templating, $view, $parameters) {
264
                $templating->stream($view, $parameters);
265
            };
266
        } elseif ($this->container->has('twig')) {
267
            $twig = $this->container->get('twig');
268
269
            $callback = static function () use ($twig, $view, $parameters) {
270
                $twig->display($view, $parameters);
271
            };
272
        } else {
273
            throw new \LogicException('You can not use the "stream" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
274
        }
275
276
        if (null === $response) {
277
            return new StreamedResponse($callback);
278
        }
279
280
        $response->setCallback($callback);
281
282
        return $response;
283
    }
284
285
    /**
286
     * Returns a NotFoundHttpException.
287
     *
288
     * This will result in a 404 response code. Usage example:
289
     *
290
     *     throw $this->createNotFoundException('Page not found!');
291
     *
292
     * @final
293
     */
294
    protected function createNotFoundException(string $message = 'Not Found', ?\Throwable $previous = null): NotFoundHttpException
295
    {
296
        return new NotFoundHttpException($message, $previous);
297
    }
298
299
    /**
300
     * Returns an AccessDeniedException.
301
     *
302
     * This will result in a 403 response code. Usage example:
303
     *
304
     *     throw $this->createAccessDeniedException('Unable to access this page!');
305
     *
306
     * @throws \LogicException If the Security component is not available
307
     *
308
     * @final
309
     */
310
    protected function createAccessDeniedException(string $message = 'Access Denied.', ?\Throwable $previous = null): AccessDeniedException
311
    {
312
        if (!class_exists(AccessDeniedException::class)) {
313
            throw new \LogicException('You can not use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".');
314
        }
315
316
        return new AccessDeniedException($message, $previous);
317
    }
318
319
    /**
320
     * Creates and returns a Form instance from the type of the form.
321
     *
322
     * @final
323
     */
324
    protected function createForm(string $type, $data = null, array $options = []): FormInterface
325
    {
326
        return $this->container->get('form.factory')->create($type, $data, $options);
327
    }
328
329
    /**
330
     * Creates and returns a form builder instance.
331
     *
332
     * @final
333
     */
334
    protected function createFormBuilder($data = null, array $options = []): FormBuilderInterface
335
    {
336
        return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options);
337
    }
338
339
    /**
340
     * Shortcut to return the Doctrine Registry service.
341
     *
342
     * @throws \LogicException If DoctrineBundle is not available
343
     *
344
     * @return ManagerRegistry
345
     *
346
     * @final
347
     */
348
    protected function getDoctrine()
349
    {
350
        if (!$this->container->has('doctrine')) {
351
            throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
352
        }
353
354
        return $this->container->get('doctrine');
355
    }
356
357
    /**
358
     * Get a user from the Security Token Storage.
359
     *
360
     * @throws \LogicException If SecurityBundle is not available
361
     *
362
     * @return UserInterface|object|null
363
     *
364
     * @see TokenInterface::getUser()
365
     *
366
     * @final
367
     */
368
    protected function getUser()
369
    {
370
        if (!$this->container->has('security.token_storage')) {
371
            throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
372
        }
373
374
        if (null === $token = $this->container->get('security.token_storage')->getToken()) {
375
            return null;
376
        }
377
378
        if (!\is_object($user = $token->getUser())) {
379
            // e.g. anonymous authentication
380
            return null;
381
        }
382
383
        return $user;
384
    }
385
386
    /**
387
     * Checks the validity of a CSRF token.
388
     *
389
     * @param string      $id    The id used when generating the token
390
     * @param string|null $token The actual token sent with the request that should be validated
391
     *
392
     * @final
393
     */
394
    protected function isCsrfTokenValid(string $id, ?string $token): bool
395
    {
396
        if (!$this->container->has('security.csrf.token_manager')) {
397
            throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".');
398
        }
399
400
        return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token));
401
    }
402
403
    /**
404
     * Dispatches a message to the bus.
405
     *
406
     * @param object|Envelope  $message The message or the message pre-wrapped in an envelope
407
     * @param StampInterface[] $stamps
408
     *
409
     * @final
410
     */
411
    protected function dispatchMessage($message, array $stamps = []): Envelope
412
    {
413
        if (!$this->container->has('messenger.default_bus')) {
414
            $message = class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".';
415
            throw new \LogicException('The message bus is not enabled in your application. '.$message);
416
        }
417
418
        return $this->container->get('messenger.default_bus')->dispatch($message, $stamps);
419
    }
420
421
    /**
422
     * Adds a Link HTTP header to the current response.
423
     *
424
     * @see https://tools.ietf.org/html/rfc5988
425
     *
426
     * @final
427
     */
428
    protected function addLink(Request $request, LinkInterface $link)
429
    {
430
        if (!class_exists(AddLinkHeaderListener::class)) {
431
            throw new \LogicException('You can not use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".');
432
        }
433
434
        if (null === $linkProvider = $request->attributes->get('_links')) {
435
            $request->attributes->set('_links', new GenericLinkProvider([$link]));
436
437
            return;
438
        }
439
440
        $request->attributes->set('_links', $linkProvider->withLink($link));
441
    }
442
}
443