• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liip / LiipImagineBundle / 22202200503

19 Feb 2026 10:10PM UTC coverage: 81.133% (+1.0%) from 80.126%
22202200503

Pull #1651

github

web-flow
Merge d7be7814b into 69d2df3c6
Pull Request #1651: Add support for alternatiwe image formats like webp and avif

247 of 266 new or added lines in 8 files covered. (92.86%)

3 existing lines in 1 file now uncovered.

2391 of 2947 relevant lines covered (81.13%)

99.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/Controller/ImagineController.php
1
<?php
2

3
/*
4
 * This file is part of the `liip/LiipImagineBundle` project.
5
 *
6
 * (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
7
 *
8
 * For the full copyright and license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11

12
namespace Liip\ImagineBundle\Controller;
13

14
use Imagine\Exception\RuntimeException;
15
use Liip\ImagineBundle\Config\Controller\ControllerConfig;
16
use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException;
17
use Liip\ImagineBundle\Exception\Imagine\Filter\NonExistingFilterException;
18
use Liip\ImagineBundle\Imagine\Cache\Helper\PathHelper;
19
use Liip\ImagineBundle\Imagine\Cache\SignerInterface;
20
use Liip\ImagineBundle\Imagine\Data\DataManager;
21
use Liip\ImagineBundle\Service\FilterService;
22
use Liip\ImagineBundle\Service\FormatNegotiator;
23
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
24
use Symfony\Component\HttpFoundation\RedirectResponse;
25
use Symfony\Component\HttpFoundation\Request;
26
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
27
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
28

29
class ImagineController
30
{
31
    /**
32
     * @var FilterService
33
     */
34
    private $filterService;
35

36
    /**
37
     * @var DataManager
38
     */
39
    private $dataManager;
40

41
    /**
42
     * @var SignerInterface
43
     */
44
    private $signer;
45

46
    /**
47
     * @var ControllerConfig
48
     */
49
    private $controllerConfig;
50

51
    /**
52
     * @var FormatNegotiator
53
     */
54
    private $formatNegotiator;
55

56
    /**
57
     * @var array
58
     */
59
    private $alternativeFormats;
60

61
    public function __construct(
62
        FilterService $filterService,
63
        DataManager $dataManager,
64
        SignerInterface $signer,
65
        ?ControllerConfig $controllerConfig = null,
66
        ?FormatNegotiator $formatNegotiator = null,
67
        array $alternativeFormats = []
68
    ) {
69
        $this->filterService = $filterService;
286✔
70
        $this->dataManager = $dataManager;
286✔
71
        $this->signer = $signer;
286✔
72

73
        if (null === $controllerConfig) {
286✔
74
            @trigger_error(\sprintf(
11✔
75
                'Instantiating "%s" without a forth argument of type "%s" is deprecated since 2.2.0 and will be required in 3.0.', self::class, ControllerConfig::class
11✔
76
            ), E_USER_DEPRECATED);
11✔
77
        }
78

79
        $this->controllerConfig = $controllerConfig ?? new ControllerConfig(301);
286✔
80
        $this->formatNegotiator = $formatNegotiator;
286✔
81
        $this->alternativeFormats = $alternativeFormats;
286✔
82
    }
83

84
    /**
85
     * This action applies a given filter to a given image, saves the image and redirects the browser to the stored
86
     * image.
87
     *
88
     * The resulting image is cached so subsequent requests will redirect to the cached image instead applying the
89
     * filter and storing the image again.
90
     *
91
     * @param string $path
92
     * @param string $filter
93
     *
94
     * @throws RuntimeException
95
     * @throws NotFoundHttpException
96
     *
97
     * @return RedirectResponse
98
     */
99
    public function filterAction(Request $request, $path, $filter)
100
    {
101
        $path = PathHelper::urlPathToFilePath($path);
198✔
102
        // TODO once we limit `symfony/http-foundation` to 6.4 or newer, use `$request->query->getString()`
103
        $resolver = $request->query->has('resolver') ? (string) $request->query->get('resolver') : null;
198✔
104

105
        return $this->createRedirectResponse(function () use ($path, $filter, $resolver, $request) {
198✔
106
            return $this->filterService->getUrlOfFilteredImage(
198✔
107
                $path,
198✔
108
                $filter,
198✔
109
                $resolver,
198✔
110
                false,
198✔
111
                $this->getAlternativeFormats($request)
198✔
112
            );
198✔
113
        }, $path, $filter);
198✔
114
    }
115

116
    /**
117
     * This action applies a given filter -merged with additional runtime filters- to a given image, saves the image and
118
     * redirects the browser to the stored image.
119
     *
120
     * The resulting image is cached so subsequent requests will redirect to the cached image instead applying the
121
     * filter and storing the image again.
122
     *
123
     * @param string $hash
124
     * @param string $path
125
     * @param string $filter
126
     *
127
     * @throws RuntimeException
128
     * @throws BadRequestHttpException
129
     * @throws NotFoundHttpException
130
     *
131
     * @return RedirectResponse
132
     */
133
    public function filterRuntimeAction(Request $request, $hash, $path, $filter)
134
    {
135
        $resolver = $request->query->has('resolver') ? (string) $request->query->get('resolver') : null;
121✔
136
        $path = PathHelper::urlPathToFilePath($path);
121✔
137
        $runtimeConfig = $this->getFiltersBc($request);
121✔
138

139
        if (true !== $this->signer->check($hash, $path, $runtimeConfig)) {
110✔
140
            throw new BadRequestHttpException(\sprintf('Signed url does not pass the sign check for path "%s" and filter "%s" and runtime config %s', $path, $filter, json_encode($runtimeConfig)));
11✔
141
        }
142

143
        return $this->createRedirectResponse(function () use ($path, $filter, $runtimeConfig, $resolver, $request) {
99✔
144
            return $this->filterService->getUrlOfFilteredImageWithRuntimeFilters(
99✔
145
                $path,
99✔
146
                $filter,
99✔
147
                $runtimeConfig,
99✔
148
                $resolver,
99✔
149
                false,
99✔
150
                $this->getAlternativeFormats($request)
99✔
151
            );
99✔
152
        }, $path, $filter, $hash);
99✔
153
    }
154

155
    private function getFiltersBc(Request $request): array
156
    {
157
        try {
158
            return $request->query->all('filters');
121✔
159
        } catch (BadRequestException $e) {
11✔
160
            // for strict BC - BadRequestException seems more suited to this situation.
161
            // remove the try-catch in version 3
162
            throw new NotFoundHttpException(\sprintf('Filters must be an array. Value was "%s"', $request->query->get('filters')));
11✔
163
        }
164
    }
165

166
    private function createRedirectResponse(\Closure $url, string $path, string $filter, ?string $hash = null): RedirectResponse
167
    {
168
        try {
169
            return new RedirectResponse($url(), $this->controllerConfig->getRedirectResponseCode());
231✔
170
        } catch (NotLoadableException $exception) {
22✔
171
            if (null !== $this->dataManager->getDefaultImageUrl($filter)) {
11✔
172
                return new RedirectResponse($this->dataManager->getDefaultImageUrl($filter));
×
173
            }
174

175
            throw new NotFoundHttpException(\sprintf('Source image for path "%s" could not be found', $path), $exception);
11✔
176
        } catch (NonExistingFilterException $exception) {
11✔
177
            throw new NotFoundHttpException(\sprintf('Requested non-existing filter "%s"', $filter), $exception);
11✔
178
        } catch (RuntimeException $exception) {
×
179
            throw new \RuntimeException(vsprintf('Unable to create image for path "%s" and filter "%s". Message was "%s"', [$hash ? \sprintf('%s/%s', $hash, $path) : $path, $filter, $exception->getMessage()]), 0, $exception);
×
180
        }
181
    }
182

183
    private function getAlternativeFormats(Request $request): array
184
    {
185
        if (null === $this->formatNegotiator) {
231✔
186
            return $this->isWebpSupported($request) ? ['webp'] : [];
66✔
187
        }
188

189
        return $this->formatNegotiator->negotiate($request, $this->alternativeFormats);
165✔
190
    }
191

192
    /**
193
     * @deprecated since 2.12, use FormatNegotiator instead.
194
     */
195
    private function isWebpSupported(Request $request): bool
196
    {
197
        if (null !== $this->formatNegotiator) {
66✔
NEW
198
            return $this->formatNegotiator->isFormatAccepted('webp', $request);
×
199
        }
200

201
        return false !== mb_stripos($request->headers->get('accept', ''), 'image/webp');
66✔
202
    }
203
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc