Completed
Push — master ( 0a7932...aee286 )
by Maksim
13s
created

CacheResolver::setDefaultOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Liip\ImagineBundle\Imagine\Cache\Resolver;
4
5
use Doctrine\Common\Cache\Cache;
6
use Liip\ImagineBundle\Binary\BinaryInterface;
7
use Symfony\Component\OptionsResolver\OptionsResolver;
8
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
9
use Symfony\Component\HttpKernel\Kernel;
10
11
class CacheResolver implements ResolverInterface
12
{
13
    /**
14
     * @var Cache
15
     */
16
    protected $cache;
17
18
    protected $options = array();
19
20
    /**
21
     * @var ResolverInterface
22
     */
23
    protected $resolver;
24
25
    /**
26
     * Constructor.
27
     *
28
     * Available options:
29
     * * global_prefix
30
     *   A prefix for all keys within the cache. This is useful to avoid colliding keys when using the same cache for different systems.
31
     * * prefix
32
     *   A "local" prefix for this wrapper. This is useful when re-using the same resolver for multiple filters.
33
     * * index_key
34
     *   The name of the index key being used to save a list of created cache keys regarding one image and filter pairing.
35
     *
36
     * @param Cache                    $cache
37
     * @param ResolverInterface        $cacheResolver
38
     * @param array                    $options
39
     * @param OptionsResolverInterface $optionsResolver
40
     */
41
    public function __construct(Cache $cache, ResolverInterface $cacheResolver, array $options = array(), OptionsResolverInterface $optionsResolver = null)
42
    {
43
        $this->cache = $cache;
44
        $this->resolver = $cacheResolver;
45
46
        if (null === $optionsResolver) {
47
            $optionsResolver = new OptionsResolver();
48
        }
49
50
        $this->configureOptions($optionsResolver);
51
        $this->options = $optionsResolver->resolve($options);
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function isStored($path, $filter)
58
    {
59
        $cacheKey = $this->generateCacheKey($path, $filter);
60
61
        return
62
            $this->cache->contains($cacheKey) ||
63
            $this->resolver->isStored($path, $filter)
64
        ;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function resolve($path, $filter)
71
    {
72
        $key = $this->generateCacheKey($path, $filter);
73
        if ($this->cache->contains($key)) {
74
            return $this->cache->fetch($key);
75
        }
76
77
        $resolved = $this->resolver->resolve($path, $filter);
78
79
        $this->saveToCache($key, $resolved);
80
81
        return $resolved;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function store(BinaryInterface $binary, $path, $filter)
88
    {
89
        $this->resolver->store($binary, $path, $filter);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function remove(array $paths, array $filters)
96
    {
97
        $this->resolver->remove($paths, $filters);
98
99
        foreach ($filters as $filter) {
100
            if (empty($paths)) {
101
                $this->removePathAndFilter(null, $filter);
102
            } else {
103
                foreach ($paths as $path) {
104
                    $this->removePathAndFilter($path, $filter);
105
                }
106
            }
107
        }
108
    }
109
110
    protected function removePathAndFilter($path, $filter)
111
    {
112
        $indexKey = $this->generateIndexKey($this->generateCacheKey($path, $filter));
113
        if (!$this->cache->contains($indexKey)) {
114
            return;
115
        }
116
117
        $index = $this->cache->fetch($indexKey);
118
119
        if (null === $path) {
120
            foreach ($index as $eachCacheKey) {
121
                $this->cache->delete($eachCacheKey);
122
            }
123
124
            $index = array();
125
        } else {
126
            $cacheKey = $this->generateCacheKey($path, $filter);
127
            if (false !== $indexIndex = array_search($cacheKey, $index)) {
128
                unset($index[$indexIndex]);
129
                $this->cache->delete($cacheKey);
130
            }
131
        }
132
133
        if (empty($index)) {
134
            $this->cache->delete($indexKey);
135
        } else {
136
            $this->cache->save($indexKey, $index);
137
        }
138
    }
139
140
    /**
141
     * Generate a unique cache key based on the given parameters.
142
     *
143
     * When overriding this method, ensure generateIndexKey is adjusted accordingly.
144
     *
145
     * @param string $path   The image path in use.
146
     * @param string $filter The filter in use.
147
     *
148
     * @return string
149
     */
150
    public function generateCacheKey($path, $filter)
151
    {
152
        return implode('.', array(
153
            $this->sanitizeCacheKeyPart($this->options['global_prefix']),
154
            $this->sanitizeCacheKeyPart($this->options['prefix']),
155
            $this->sanitizeCacheKeyPart($filter),
156
            $this->sanitizeCacheKeyPart($path),
157
        ));
158
    }
159
160
    /**
161
     * Generate the index key for the given cacheKey.
162
     *
163
     * The index contains a list of cache keys related to an image and a filter.
164
     *
165
     * @param string $cacheKey
166
     *
167
     * @return string
168
     */
169
    protected function generateIndexKey($cacheKey)
170
    {
171
        $cacheKeyStack = explode('.', $cacheKey);
172
173
        return implode('.', array(
174
            $this->sanitizeCacheKeyPart($this->options['global_prefix']),
175
            $this->sanitizeCacheKeyPart($this->options['prefix']),
176
            $this->sanitizeCacheKeyPart($this->options['index_key']),
177
            $this->sanitizeCacheKeyPart($cacheKeyStack[2]), // filter
178
        ));
179
    }
180
181
    /**
182
     * @param string $cacheKeyPart
183
     *
184
     * @return string
185
     */
186
    protected function sanitizeCacheKeyPart($cacheKeyPart)
187
    {
188
        return str_replace('.', '_', $cacheKeyPart);
189
    }
190
191
    /**
192
     * Save the given content to the cache and update the cache index.
193
     *
194
     * @param string $cacheKey
195
     * @param mixed  $content
196
     *
197
     * @return bool
198
     */
199
    protected function saveToCache($cacheKey, $content)
200
    {
201
        // Create or update the index list containing all cache keys for a given image and filter pairing.
202
        $indexKey = $this->generateIndexKey($cacheKey);
203
        if ($this->cache->contains($indexKey)) {
204
            $index = (array) $this->cache->fetch($indexKey);
205
206
            if (!in_array($cacheKey, $index)) {
207
                $index[] = $cacheKey;
208
            }
209
        } else {
210
            $index = array($cacheKey);
211
        }
212
213
        /*
214
         * Only save the content, if the index has been updated successfully.
215
         * This is required to have a (hopefully) synchron state between cache and backend.
216
         *
217
         * "Hopefully" because there are caches (like Memcache) which will remove keys by themselves.
218
         */
219
        if ($this->cache->save($indexKey, $index)) {
220
            return $this->cache->save($cacheKey, $content);
221
        }
222
223
        return false;
224
    }
225
226
    protected function configureOptions(OptionsResolver $resolver)
227
    {
228
        $resolver->setDefaults(array(
229
            'global_prefix' => 'liip_imagine.resolver_cache',
230
            'prefix' => get_class($this->resolver),
231
            'index_key' => 'index',
232
        ));
233
234
        $allowedTypesList = array(
235
          'global_prefix' => 'string',
236
          'prefix' => 'string',
237
          'index_key' => 'string',
238
        );
239
240
        if (version_compare(Kernel::VERSION_ID, '20600') >= 0) {
241
            foreach ($allowedTypesList as $option => $allowedTypes) {
242
                $resolver->setAllowedTypes($option, $allowedTypes);
243
            }
244
        } else {
245
            $resolver->setAllowedTypes($allowedTypesList);
0 ignored issues
show
Bug introduced by
The call to setAllowedTypes() misses a required argument $allowedTypes.

This check looks for function calls that miss required arguments.

Loading history...
Documentation introduced by
$allowedTypesList is of type array<string,string,{"gl...,"index_key":"string"}>, but the function expects a string.

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...
246
        }
247
    }
248
249
    protected function setDefaultOptions(OptionsResolverInterface $resolver)
250
    {
251
        $this->configureOptions($resolver);
252
    }
253
}
254