Completed
Push — master ( c9dcb0...df6719 )
by David
12s queued 10s
created

CacheManager::dispatchWithBC()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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\Imagine\Cache;
13
14
use Liip\ImagineBundle\Binary\BinaryInterface;
15
use Liip\ImagineBundle\Events\CacheResolveEvent;
16
use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface;
17
use Liip\ImagineBundle\Imagine\Filter\FilterConfiguration;
18
use Liip\ImagineBundle\ImagineEvents;
19
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
20
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
21
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
22
use Symfony\Component\Routing\RouterInterface;
23
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
24
25
class CacheManager
26
{
27
    /**
28
     * @var FilterConfiguration
29
     */
30
    protected $filterConfig;
31
32
    /**
33
     * @var RouterInterface
34
     */
35
    protected $router;
36
37
    /**
38
     * @var ResolverInterface[]
39
     */
40
    protected $resolvers = [];
41
42
    /**
43
     * @var SignerInterface
44
     */
45
    protected $signer;
46
47
    /**
48
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
49
     */
50
    protected $dispatcher;
51
52
    /**
53
     * @var string
54
     */
55
    protected $defaultResolver;
56
57
    /**
58
     * Constructs the cache manager to handle Resolvers based on the provided FilterConfiguration.
59
     *
60
     * @param FilterConfiguration      $filterConfig
61
     * @param RouterInterface          $router
62
     * @param SignerInterface          $signer
63
     * @param EventDispatcherInterface $dispatcher
64
     * @param string                   $defaultResolver
65
     */
66
    public function __construct(
67
        FilterConfiguration $filterConfig,
68
        RouterInterface $router,
69
        SignerInterface $signer,
70
        EventDispatcherInterface $dispatcher,
71
        $defaultResolver = null
72
    ) {
73
        $this->filterConfig = $filterConfig;
74
        $this->router = $router;
75
        $this->signer = $signer;
76
        $this->dispatcher = $dispatcher;
77
        $this->defaultResolver = $defaultResolver ?: 'default';
78
    }
79
80
    /**
81
     * Adds a resolver to handle cached images for the given filter.
82
     *
83
     * @param string            $filter
84
     * @param ResolverInterface $resolver
85
     */
86
    public function addResolver($filter, ResolverInterface $resolver)
87
    {
88
        $this->resolvers[$filter] = $resolver;
89
90
        if ($resolver instanceof CacheManagerAwareInterface) {
91
            $resolver->setCacheManager($this);
92
        }
93
    }
94
95
    /**
96
     * Gets filtered path for rendering in the browser.
97
     * It could be the cached one or an url of filter action.
98
     *
99
     * @param string $path          The path where the resolved file is expected
100
     * @param string $filter
101
     * @param array  $runtimeConfig
102
     * @param string $resolver
103
     *
104
     * @return string
105
     */
106
    public function getBrowserPath($path, $filter, array $runtimeConfig = [], $resolver = null)
107
    {
108
        if (!empty($runtimeConfig)) {
109
            $rcPath = $this->getRuntimePath($path, $runtimeConfig);
110
111
            return $this->isStored($rcPath, $filter, $resolver) ?
112
                $this->resolve($rcPath, $filter, $resolver) :
113
                $this->generateUrl($path, $filter, $runtimeConfig, $resolver);
114
        }
115
116
        return $this->isStored($path, $filter, $resolver) ?
117
            $this->resolve($path, $filter, $resolver) :
118
            $this->generateUrl($path, $filter, [], $resolver);
119
    }
120
121
    /**
122
     * Get path to runtime config image.
123
     *
124
     * @param string $path
125
     * @param array  $runtimeConfig
126
     *
127
     * @return string
128
     */
129
    public function getRuntimePath($path, array $runtimeConfig)
130
    {
131
        return 'rc/'.$this->signer->sign($path, $runtimeConfig).'/'.$path;
132
    }
133
134
    /**
135
     * Returns a web accessible URL.
136
     *
137
     * @param string $path          The path where the resolved file is expected
138
     * @param string $filter        The name of the imagine filter in effect
139
     * @param array  $runtimeConfig
140
     * @param string $resolver
141
     *
142
     * @return string
143
     */
144
    public function generateUrl($path, $filter, array $runtimeConfig = [], $resolver = null)
145
    {
146
        $params = [
147
            'path' => ltrim($path, '/'),
148
            'filter' => $filter,
149
        ];
150
151
        if ($resolver) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resolver of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
152
            $params['resolver'] = $resolver;
153
        }
154
155
        if (empty($runtimeConfig)) {
156
            $filterUrl = $this->router->generate('liip_imagine_filter', $params, UrlGeneratorInterface::ABSOLUTE_URL);
157
        } else {
158
            $params['filters'] = $runtimeConfig;
159
            $params['hash'] = $this->signer->sign($path, $runtimeConfig);
160
161
            $filterUrl = $this->router->generate('liip_imagine_filter_runtime', $params, UrlGeneratorInterface::ABSOLUTE_URL);
162
        }
163
164
        return $filterUrl;
165
    }
166
167
    /**
168
     * Checks whether the path is already stored within the respective Resolver.
169
     *
170
     * @param string $path
171
     * @param string $filter
172
     * @param string $resolver
173
     *
174
     * @return bool
175
     */
176
    public function isStored($path, $filter, $resolver = null)
177
    {
178
        return $this->getResolver($filter, $resolver)->isStored($path, $filter);
179
    }
180
181
    /**
182
     * Resolves filtered path for rendering in the browser.
183
     *
184
     * @param string $path
185
     * @param string $filter
186
     * @param string $resolver
187
     *
188
     * @throws NotFoundHttpException if the path can not be resolved
189
     *
190
     * @return string The url of resolved image
191
     */
192
    public function resolve($path, $filter, $resolver = null)
193
    {
194
        if (false !== mb_strpos($path, '/../') || 0 === mb_strpos($path, '../')) {
195
            throw new NotFoundHttpException(sprintf("Source image was searched with '%s' outside of the defined root path", $path));
196
        }
197
198
        $preEvent = new CacheResolveEvent($path, $filter);
199
        $this->dispatchWithBC($preEvent, ImagineEvents::PRE_RESOLVE);
200
201
        $url = $this->getResolver($preEvent->getFilter(), $resolver)->resolve($preEvent->getPath(), $preEvent->getFilter());
202
203
        $postEvent = new CacheResolveEvent($preEvent->getPath(), $preEvent->getFilter(), $url);
204
        $this->dispatchWithBC($postEvent, ImagineEvents::POST_RESOLVE);
205
206
        return $postEvent->getUrl();
207
    }
208
209
    /**
210
     * BC Layer for Symfony < 4.3
211
     *
212
     * @param CacheResolveEvent $event
213
     * @param string $eventName
214
     */
215
    private function dispatchWithBC(CacheResolveEvent $event, string $eventName): void
216
    {
217
        if ($this->dispatcher instanceof ContractsEventDispatcherInterface) {
218
            $this->dispatcher->dispatch($event, $eventName);
219
        } else {
220
            $this->dispatcher->dispatch($eventName, $event);
221
        }
222
    }
223
224
    /**
225
     * @see ResolverInterface::store
226
     *
227
     * @param BinaryInterface $binary
228
     * @param string          $path
229
     * @param string          $filter
230
     * @param string          $resolver
231
     */
232
    public function store(BinaryInterface $binary, $path, $filter, $resolver = null)
233
    {
234
        $this->getResolver($filter, $resolver)->store($binary, $path, $filter);
235
    }
236
237
    /**
238
     * @param string|string[]|null $paths
239
     * @param string|string[]|null $filters
240
     */
241
    public function remove($paths = null, $filters = null)
242
    {
243
        if (null === $filters) {
244
            $filters = array_keys($this->filterConfig->all());
245
        } elseif (!\is_array($filters)) {
246
            $filters = [$filters];
247
        }
248
        if (!\is_array($paths)) {
249
            $paths = [$paths];
250
        }
251
252
        $paths = array_filter($paths);
253
        $filters = array_filter($filters);
254
255
        $mapping = new \SplObjectStorage();
256
        foreach ($filters as $filter) {
257
            $resolver = $this->getResolver($filter, null);
258
259
            $list = isset($mapping[$resolver]) ? $mapping[$resolver] : [];
260
261
            $list[] = $filter;
262
263
            $mapping[$resolver] = $list;
264
        }
265
266
        foreach ($mapping as $resolver) {
267
            $resolver->remove($paths, $mapping[$resolver]);
268
        }
269
    }
270
271
    /**
272
     * Gets a resolver for the given filter.
273
     *
274
     * In case there is no specific resolver, but a default resolver has been configured, the default will be returned.
275
     *
276
     * @param string $filter
277
     * @param string $resolver
278
     *
279
     * @throws \OutOfBoundsException If neither a specific nor a default resolver is available
280
     *
281
     * @return ResolverInterface
282
     */
283
    protected function getResolver($filter, $resolver)
284
    {
285
        // BC
286
        if (!$resolver) {
287
            $config = $this->filterConfig->get($filter);
288
289
            $resolverName = empty($config['cache']) ? $this->defaultResolver : $config['cache'];
290
        } else {
291
            $resolverName = $resolver;
292
        }
293
294
        if (!isset($this->resolvers[$resolverName])) {
295
            throw new \OutOfBoundsException(sprintf(
296
                'Could not find resolver "%s" for "%s" filter type',
297
                $resolverName,
298
                $filter
299
            ));
300
        }
301
302
        return $this->resolvers[$resolverName];
303
    }
304
}
305