Manager   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 4
dl 0
loc 262
ccs 107
cts 107
cp 1
rs 8.295
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getMethodCaller() 0 8 2
A setMethodCaller() 0 4 1
A localUrl() 0 3 1
A url() 0 4 1
A imageUrl() 0 4 1
A isRetina() 0 4 1
A getOriginalFilename() 0 5 2
B getPresetActions() 0 27 5
A generateRetinaAction() 0 10 4
A loadImage() 0 4 1
A getOriginalFile() 0 9 2
A handleRequest() 0 23 2
A verifyDirectoryExistence() 0 15 4
B buildImage() 0 26 6
A percent() 0 8 2
B keywords() 0 18 6

How to fix   Complexity   

Complex Class

Complex classes like Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Image Manager
5
 */
6
namespace Onigoetz\Imagecache;
7
8
/**
9
 * Image manager
10
 *
11
 * Prepares the images for the cache
12
 *
13
 *
14
 * @author Stéphane Goetz
15
 */
16
class Manager
17
{
18
    /**
19
     * @var array Contains configurations and presets
20
     */
21
    protected $options;
22
23
    /**
24
     * @var MethodCaller
25
     */
26
    protected $methodCaller;
27
28
    /**
29
     * @var string
30
     */
31
    protected $retinaRegex = '/(.*)@2x\\.(jpe?g|png|webp|gif)/';
32
33 153
    public function __construct($options)
34
    {
35 153
        $this->options = $options + ['path_web' => 'images', 'path_cache' => 'cache'];
36 153
    }
37
38
    /**
39
     * @return MethodCaller
40
     */
41 54
    public function getMethodCaller()
42
    {
43 54
        if (!$this->methodCaller) {
44 51
            $this->methodCaller = new MethodCaller();
45 17
        }
46
47 54
        return $this->methodCaller;
48
    }
49
50
    /**
51
     * @param MethodCaller $methodCaller
52
     */
53 3
    public function setMethodCaller(MethodCaller $methodCaller)
54
    {
55 3
        $this->methodCaller = $methodCaller;
56 3
    }
57
58 12
    public function localUrl($preset, $file) {
59 12
        return "{$this->options['path_cache']}/$preset/$file";
60
    }
61
62 3
    public function url($preset, $file)
63
    {
64 3
        return "{$this->options['path_web']}/{$this->options['path_cache']}/$preset/$file";
65
    }
66
67 3
    public function imageUrl($file)
68
    {
69 3
        return "{$this->options['path_web']}/$file";
70
    }
71
72 36
    public function isRetina($file)
73
    {
74 36
        return !!preg_match($this->retinaRegex, $file);
75
    }
76
77 39
    public function getOriginalFilename($file) {
78 39
        $matched = preg_match($this->retinaRegex, $file, $matches);
79
80 39
        return ($matched)? $matches[1] . '.' . $matches[2] : $file;
81
    }
82
83 36
    protected function getPresetActions($preset_key, $file)
84
    {
85
        // Is it a valid preset
86 36
        if (!array_key_exists($preset_key, $this->options['presets'])) {
87 9
            throw new Exceptions\InvalidPresetException('invalid preset');
88
        }
89
90 27
        $preset = $this->options['presets'][$preset_key];
91
92 27
        if (!$this->isRetina($file)) {
93 21
            return $preset;
94
        }
95
96
        // Handle retina images
97
98 6
        $preset_key = "$preset_key@2x";
99
100 6
        if (array_key_exists($preset_key, $this->options['presets'])) {
101 3
            return $this->options['presets'][$preset_key];
102
        }
103
104 3
        foreach ($preset as &$action) {
105 3
            $action = $this->generateRetinaAction($action);
106 1
        }
107
108 3
        return $preset;
109
    }
110
111 27
    protected function generateRetinaAction($action)
112
    {
113 27
        foreach (['width', 'height', 'xoffset', 'yoffset'] as $option) {
114 27
            if (array_key_exists($option, $action) && is_numeric($action[$option])) {
115 25
                $action[$option] *= 2;
116 8
            }
117 9
        }
118
119 27
        return $action;
120
    }
121
122
    /**
123
     * The path to the original file
124
     * @param $source_file
125
     * @return string
126
     * @throws Exceptions\NotFoundException
127
     */
128 18
    public function getOriginalFile($source_file)
129
    {
130 18
        $original_file = $this->options['path_local'] . '/' . $source_file;
131 18
        if (!is_file($original_file)) {
132 6
            throw new Exceptions\NotFoundException('File not found');
133
        }
134
135 12
        return $original_file;
136
    }
137
138
    /**
139
     * Take a preset and a file and return a transformed image
140
     *
141
     * @param $preset_key string
142
     * @param $file string
143
     * @throws Exceptions\InvalidPresetException
144
     * @throws Exceptions\NotFoundException
145
     * @throws \RuntimeException
146
     * @return string
147
     */
148 42
    public function handleRequest($preset_key, $file)
149
    {
150
        //do it at the beginning for early validation
151 42
        $preset = $this->getPresetActions($preset_key, $file);
152
153 36
        $source_file =  $this->getOriginalFilename($file);
154
155 36
        $original_file = $this->getOriginalFile($source_file);
156
157 27
        $final_file = $this->localUrl($preset_key, $file);
158
159 27
        $this->verifyDirectoryExistence($this->options['path_local'], dirname($final_file));
160
161 27
        $final_file = $this->options['path_local'] . '/' . $final_file;
162
163 27
        if (file_exists($final_file)) {
164 6
            return $final_file;
165
        }
166
167 24
        $image = $this->loadImage($original_file);
168
169 15
        return $this->buildImage($preset, $image, $final_file)->source;
170
    }
171
172
    /**
173
     * Create the folder containing the cached images if it doesn't exist
174
     *
175
     * @param $base
176
     * @param $cacheDir
177
     */
178 15
    protected function verifyDirectoryExistence($base, $cacheDir)
179
    {
180 15
        if (is_dir("$base/$cacheDir")) {
181 3
            return;
182
        }
183
184 15
        $folder_path = explode('/', $cacheDir);
185 15
        foreach ($folder_path as $element) {
186 15
            $base .= '/' . $element;
187 15
            if (!is_dir($base)) {
188 15
                mkdir($base, 0755, true);
189 15
                chmod($base, 0755);
190 5
            }
191 5
        }
192 15
    }
193
194 15
    protected function loadImage($src)
195
    {
196 15
        return new Image($src);
197
    }
198
199
    /**
200
     * Create a new image based on an image preset.
201
     *
202
     * @param array $actions An image preset array.
203
     * @param Image $image Path of the source file.
204
     * @param string $dst Path of the destination file.
205
     * @throws \RuntimeException
206
     * @return Image
207
     */
208 78
    protected function buildImage($actions, Image $image, $dst)
209
    {
210 78
        foreach ($actions as $action) {
211
            // Make sure the width and height are computed first so they can be used
212
            // in relative x/yoffsets like 'center' or 'bottom'.
213 78
            if (isset($action['width'])) {
214 60
                $action['width'] = $this->percent($action['width'], $image->getWidth());
215 20
            }
216
217 78
            if (isset($action['height'])) {
218 51
                $action['height'] = $this->percent($action['height'], $image->getHeight());
219 17
            }
220
221 78
            if (isset($action['xoffset'])) {
222 15
                $action['xoffset'] = $this->keywords($action['xoffset'], $image->getWidth(), $action['width']);
223 5
            }
224
225 78
            if (isset($action['yoffset'])) {
226 15
                $action['yoffset'] = $this->keywords($action['yoffset'], $image->getHeight(), $action['height']);
227 5
            }
228
229 78
            $this->getMethodCaller()->call($image, $action['action'], $action);
230 24
        }
231
232 72
        return $image->save($dst);
233
    }
234
235
    /**
236
     * Accept a percentage and return it in pixels.
237
     *
238
     * @param  string $value
239
     * @param  int $current_pixels
240
     * @return mixed
241
     */
242 51
    public function percent($value, $current_pixels)
243
    {
244 51
        if (strpos($value, '%') !== false) {
245 9
            $value = str_replace('%', '', $value) * 0.01 * $current_pixels;
246 3
        }
247
248 51
        return $value;
249
    }
250
251
    /**
252
     * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
253
     *
254
     * @param $value
255
     * @param $current_pixels
256
     * @param $new_pixels
257
     * @return float|int
258
     */
259 27
    public function keywords($value, $current_pixels, $new_pixels)
260
    {
261
        switch ($value) {
262 27
            case 'top':
263 25
            case 'left':
264 6
                $value = 0;
265 6
                break;
266 21
            case 'bottom':
267 19
            case 'right':
268 6
                $value = $current_pixels - $new_pixels;
269 6
                break;
270 15
            case 'center':
271 3
                $value = $current_pixels / 2 - $new_pixels / 2;
272 3
                break;
273
        }
274
275 27
        return $value;
276
    }
277
}
278