Completed
Push — master ( a5c5e5...2d8f65 )
by Stéphane
03:02
created

Manager   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 10
Bugs 3 Features 1
Metric Value
wmc 40
c 10
b 3
f 1
lcom 1
cbo 4
dl 0
loc 245
ccs 102
cts 102
cp 1
rs 8.2608

15 Methods

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