1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.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
12namespace Symfony\Component\HttpKernel\Fragment;
13
14use Symfony\Component\HttpFoundation\RequestStack;
15use Symfony\Component\HttpFoundation\Response;
16use Symfony\Component\HttpFoundation\StreamedResponse;
17use Symfony\Component\HttpKernel\Controller\ControllerReference;
18
19/**
20 * Renders a URI that represents a resource fragment.
21 *
22 * This class handles the rendering of resource fragments that are included into
23 * a main resource. The handling of the rendering is managed by specialized renderers.
24 *
25 * @author Fabien Potencier <fabien@symfony.com>
26 *
27 * @see FragmentRendererInterface
28 */
29class FragmentHandler
30{
31    private $debug;
32    private $renderers = [];
33    private $requestStack;
34
35    /**
36     * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances
37     * @param bool                        $debug     Whether the debug mode is enabled or not
38     */
39    public function __construct(RequestStack $requestStack, array $renderers = [], bool $debug = false)
40    {
41        $this->requestStack = $requestStack;
42        foreach ($renderers as $renderer) {
43            $this->addRenderer($renderer);
44        }
45        $this->debug = $debug;
46    }
47
48    /**
49     * Adds a renderer.
50     */
51    public function addRenderer(FragmentRendererInterface $renderer)
52    {
53        $this->renderers[$renderer->getName()] = $renderer;
54    }
55
56    /**
57     * Renders a URI and returns the Response content.
58     *
59     * Available options:
60     *
61     *  * ignore_errors: true to return an empty string in case of an error
62     *
63     * @param string|ControllerReference $uri      A URI as a string or a ControllerReference instance
64     * @param string                     $renderer The renderer name
65     *
66     * @return string|null The Response content or null when the Response is streamed
67     *
68     * @throws \InvalidArgumentException when the renderer does not exist
69     * @throws \LogicException           when no master request is being handled
70     */
71    public function render($uri, $renderer = 'inline', array $options = [])
72    {
73        if (!isset($options['ignore_errors'])) {
74            $options['ignore_errors'] = !$this->debug;
75        }
76
77        if (!isset($this->renderers[$renderer])) {
78            throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
79        }
80
81        if (!$request = $this->requestStack->getCurrentRequest()) {
82            throw new \LogicException('Rendering a fragment can only be done when handling a Request.');
83        }
84
85        return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options));
86    }
87
88    /**
89     * Delivers the Response as a string.
90     *
91     * When the Response is a StreamedResponse, the content is streamed immediately
92     * instead of being returned.
93     *
94     * @return string|null The Response content or null when the Response is streamed
95     *
96     * @throws \RuntimeException when the Response is not successful
97     */
98    protected function deliver(Response $response)
99    {
100        if (!$response->isSuccessful()) {
101            throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode()));
102        }
103
104        if (!$response instanceof StreamedResponse) {
105            return $response->getContent();
106        }
107
108        $response->sendContent();
109
110        return null;
111    }
112}
113