CacheManager::remove()   B
last analyzed

Complexity

Conditions 7
Paths 36

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 29
rs 8.5226
cc 7
nc 36
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 string $defaultResolver
61
     */
62
    public function __construct(
63
        FilterConfiguration $filterConfig,
64
        RouterInterface $router,
65
        SignerInterface $signer,
66
        EventDispatcherInterface $dispatcher,
67
        $defaultResolver = null
68
    ) {
69
        $this->filterConfig = $filterConfig;
70
        $this->router = $router;
71
        $this->signer = $signer;
72
        $this->dispatcher = $dispatcher;
73
        $this->defaultResolver = $defaultResolver ?: 'default';
74
    }
75
76
    /**
77
     * Adds a resolver to handle cached images for the given filter.
78
     *
79
     * @param string $filter
80
     */
81
    public function addResolver($filter, ResolverInterface $resolver)
82
    {
83
        $this->resolvers[$filter] = $resolver;
84
85
        if ($resolver instanceof CacheManagerAwareInterface) {
86
            $resolver->setCacheManager($this);
87
        }
88
    }
89
90
    /**
91
     * Gets filtered path for rendering in the browser.
92
     * It could be the cached one or an url of filter action.
93
     *
94
     * @param string $path          The path where the resolved file is expected
95
     * @param string $filter
96
     * @param string $resolver
97
     * @param int    $referenceType
98
     *
99
     * @return string
100
     */
101
    public function getBrowserPath($path, $filter, array $runtimeConfig = [], $resolver = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_URL)
102
    {
103
        if (!empty($runtimeConfig)) {
104
            $rcPath = $this->getRuntimePath($path, $runtimeConfig);
105
106
            return $this->isStored($rcPath, $filter, $resolver) ?
107
                $this->resolve($rcPath, $filter, $resolver) :
108
                $this->generateUrl($path, $filter, $runtimeConfig, $resolver, $referenceType);
109
        }
110
111
        return $this->isStored($path, $filter, $resolver) ?
112
            $this->resolve($path, $filter, $resolver) :
113
            $this->generateUrl($path, $filter, [], $resolver, $referenceType);
114
    }
115
116
    /**
117
     * Get path to runtime config image.
118
     *
119
     * @param string $path
120
     *
121
     * @return string
122
     */
123
    public function getRuntimePath($path, array $runtimeConfig)
124
    {
125
        return 'rc/'.$this->signer->sign($path, $runtimeConfig).'/'.$path;
126
    }
127
128
    /**
129
     * Returns a web accessible URL.
130
     *
131
     * @param string $path          The path where the resolved file is expected
132
     * @param string $filter        The name of the imagine filter in effect
133
     * @param string $resolver
134
     * @param int    $referenceType The type of reference to be generated (one of the UrlGenerator constants)
135
     *
136
     * @return string
137
     */
138
    public function generateUrl($path, $filter, array $runtimeConfig = [], $resolver = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_URL)
139
    {
140
        $params = [
141
            'path' => ltrim($path, '/'),
142
            'filter' => $filter,
143
        ];
144
145
        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...
146
            $params['resolver'] = $resolver;
147
        }
148
149
        if (empty($runtimeConfig)) {
150
            $filterUrl = $this->router->generate('liip_imagine_filter', $params, $referenceType);
151
        } else {
152
            $params['filters'] = $runtimeConfig;
153
            $params['hash'] = $this->signer->sign($path, $runtimeConfig);
154
155
            $filterUrl = $this->router->generate('liip_imagine_filter_runtime', $params, $referenceType);
156
        }
157
158
        return $filterUrl;
159
    }
160
161
    /**
162
     * Checks whether the path is already stored within the respective Resolver.
163
     *
164
     * @param string $path
165
     * @param string $filter
166
     * @param string $resolver
167
     *
168
     * @return bool
169
     */
170
    public function isStored($path, $filter, $resolver = null)
171
    {
172
        return $this->getResolver($filter, $resolver)->isStored($path, $filter);
173
    }
174
175
    /**
176
     * Resolves filtered path for rendering in the browser.
177
     *
178
     * @param string $path
179
     * @param string $filter
180
     * @param string $resolver
181
     *
182
     * @throws NotFoundHttpException if the path can not be resolved
183
     *
184
     * @return string The url of resolved image
185
     */
186
    public function resolve($path, $filter, $resolver = null)
187
    {
188
        if (false !== mb_strpos($path, '/../') || 0 === mb_strpos($path, '../')) {
189
            throw new NotFoundHttpException(sprintf("Source image was searched with '%s' outside of the defined root path", $path));
190
        }
191
192
        $preEvent = new CacheResolveEvent($path, $filter);
193
        $this->dispatchWithBC($preEvent, ImagineEvents::PRE_RESOLVE);
194
195
        $url = $this->getResolver($preEvent->getFilter(), $resolver)->resolve($preEvent->getPath(), $preEvent->getFilter());
196
197
        $postEvent = new CacheResolveEvent($preEvent->getPath(), $preEvent->getFilter(), $url);
198
        $this->dispatchWithBC($postEvent, ImagineEvents::POST_RESOLVE);
199
200
        return $postEvent->getUrl();
201
    }
202
203
    /**
204
     * @see ResolverInterface::store
205
     *
206
     * @param string $path
207
     * @param string $filter
208
     * @param string $resolver
209
     */
210
    public function store(BinaryInterface $binary, $path, $filter, $resolver = null)
211
    {
212
        $this->getResolver($filter, $resolver)->store($binary, $path, $filter);
213
    }
214
215
    /**
216
     * @param string|string[]|null $paths
217
     * @param string|string[]|null $filters
218
     */
219
    public function remove($paths = null, $filters = null)
220
    {
221
        if (null === $filters) {
222
            $filters = array_keys($this->filterConfig->all());
223
        } elseif (!\is_array($filters)) {
224
            $filters = [$filters];
225
        }
226
        if (!\is_array($paths)) {
227
            $paths = [$paths];
228
        }
229
230
        $paths = array_filter($paths);
231
        $filters = array_filter($filters);
232
233
        $mapping = new \SplObjectStorage();
234
        foreach ($filters as $filter) {
235
            $resolver = $this->getResolver($filter, null);
236
237
            $list = isset($mapping[$resolver]) ? $mapping[$resolver] : [];
238
239
            $list[] = $filter;
240
241
            $mapping[$resolver] = $list;
242
        }
243
244
        foreach ($mapping as $resolver) {
245
            $resolver->remove($paths, $mapping[$resolver]);
246
        }
247
    }
248
249
    /**
250
     * Gets a resolver for the given filter.
251
     *
252
     * In case there is no specific resolver, but a default resolver has been configured, the default will be returned.
253
     *
254
     * @param string $filter
255
     * @param string $resolver
256
     *
257
     * @throws \OutOfBoundsException If neither a specific nor a default resolver is available
258
     *
259
     * @return ResolverInterface
260
     */
261
    protected function getResolver($filter, $resolver)
262
    {
263
        // BC
264
        if (!$resolver) {
265
            $config = $this->filterConfig->get($filter);
266
267
            $resolverName = empty($config['cache']) ? $this->defaultResolver : $config['cache'];
268
        } else {
269
            $resolverName = $resolver;
270
        }
271
272
        if (!isset($this->resolvers[$resolverName])) {
273
            throw new \OutOfBoundsException(sprintf('Could not find resolver "%s" for "%s" filter type', $resolverName, $filter));
274
        }
275
276
        return $this->resolvers[$resolverName];
277
    }
278
279
    /**
280
     * BC Layer for Symfony < 4.3
281
     */
282
    private function dispatchWithBC(CacheResolveEvent $event, string $eventName): void
283
    {
284
        if ($this->dispatcher instanceof ContractsEventDispatcherInterface) {
285
            $this->dispatcher->dispatch($event, $eventName);
0 ignored issues
show
Documentation introduced by
$event is of type object<Liip\ImagineBundl...ents\CacheResolveEvent>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
286
        } else {
287
            $this->dispatcher->dispatch($eventName, $event);
288
        }
289
    }
290
}
291