Completed
Push — master ( c1ddd1...39fcfb )
by Bjørn
03:00
created

AbstractConverter::doConvertImplementation()   B

Complexity

Conditions 8
Paths 24

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 8.1715

Importance

Changes 0
Metric Value
cc 8
eloc 39
nc 24
nop 0
dl 0
loc 62
ccs 31
cts 36
cp 0.8611
crap 8.1715
rs 8.0515
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// TODO:
4
// Read this: https://sourcemaking.com/design_patterns/strategy
5
6
namespace WebPConvert\Convert\Converters;
7
8
use WebPConvert\Convert\Exceptions\ConversionFailedException;
9
use WebPConvert\Exceptions\WebPConvertException;
10
use WebPConvert\Convert\Converters\BaseTraits\AutoQualityTrait;
11
use WebPConvert\Convert\Converters\BaseTraits\DestinationPreparationTrait;
12
use WebPConvert\Convert\Converters\BaseTraits\LoggerTrait;
13
use WebPConvert\Convert\Converters\BaseTraits\OptionsTrait;
14
use WebPConvert\Convert\Converters\BaseTraits\SourceValidationTrait;
15
use WebPConvert\Convert\Converters\BaseTraits\WarningLoggerTrait;
16
use WebPConvert\Loggers\BaseLogger;
17
18
use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
19
20
/**
21
 * Base for all converter classes.
22
 *
23
 * @package    WebPConvert
24
 * @author     Bjørn Rosell <[email protected]>
25
 * @since      Class available since Release 2.0.0
26
 */
27
abstract class AbstractConverter
28
{
29
    use AutoQualityTrait;
30
    use LoggerTrait;
31
    use OptionsTrait;
32
    use WarningLoggerTrait;
33
    use DestinationPreparationTrait;
34
    use SourceValidationTrait;
35
36
    /**
37
     * The actual conversion is be done by a concrete converter extending this class.
38
     *
39
     * At the stage this method is called, the abstract converter has taken preparational steps.
40
     * - It has created the destination folder (if neccesary)
41
     * - It has checked the input (valid mime type)
42
     * - It has set up an error handler, mostly in order to catch and log warnings during the doConvert fase
43
     *
44
     * Note: This method is not meant to be called from the outside. Use the static *convert* method for converting
45
     *       or, if you wish, create an instance with ::createInstance() and then call ::doConvert()
46
     *
47
     * @throws ConversionFailedException in case conversion failed in an antipiciated way (or subclass)
48
     * @throws \Exception in case conversion failed in an unantipiciated way
49
     */
50
    abstract protected function doActualConvert();
51
52
    /**
53
     * Whether or not the converter supports lossless encoding (even for jpegs)
54
     *
55
     * PS: Converters that supports lossless encoding all use the EncodingAutoTrait, which
56
     * overrides this function.
57
     *
58
     * @return  boolean  Whether the converter supports lossless encoding (even for jpegs).
59
     */
60
    public function supportsLossless()
61
    {
62
        return false;
63
    }
64
65
    /** @var string  The filename of the image to convert (complete path) */
66
    protected $source;
67
68
    /** @var string  Where to save the webp (complete path) */
69
    protected $destination;
70
71
    /** @var string|false|null  Where to save the webp (complete path) */
72
    private $sourceMimeType;
73
74
    /**
75
     * Check basis operationality
76
     *
77
     * Converters may override this method for the purpose of performing basic operationaly checks. It is for
78
     * running general operation checks for a conversion method.
79
     * If some requirement is not met, it should throw a ConverterNotOperationalException (or subtype)
80
     *
81
     * The method is called internally right before calling doActualConvert() method.
82
     * - It SHOULD take options into account when relevant. For example, a missing api key for a
83
     *   cloud converter should be detected here
84
     * - It should NOT take the actual filename into consideration, as the purpose is *general*
85
     *   For that pupose, converters should override checkConvertability
86
     *   Also note that doConvert method is allowed to throw ConverterNotOperationalException too.
87
     *
88
     * @return  void
89
     */
90 2
    public function checkOperationality()
91
    {
92 2
    }
93
94
    /**
95
     * Converters may override this for the purpose of performing checks on the concrete file.
96
     *
97
     * This can for example be used for rejecting big uploads in cloud converters or rejecting unsupported
98
     * image types.
99
     *
100
     * @return  void
101
     */
102 5
    public function checkConvertability()
103
    {
104 5
    }
105
106
    /**
107
     * Constructor.
108
     *
109
     * @param   string  $source              path to source file
110
     * @param   string  $destination         path to destination
111
     * @param   array   $options (optional)  options for conversion
112
     * @param   BaseLogger $logger (optional)
113
     */
114 31
    public function __construct($source, $destination, $options = [], $logger = null)
115
    {
116 31
        $this->source = $source;
117 31
        $this->destination = $destination;
118
119 31
        $this->setLogger($logger);
120 31
        $this->setProvidedOptions($options);
121
122 31
        $this->checkSourceExists();
123 30
        $this->checkSourceMimeType();
124 30
    }
125
126
    /**
127
     * Get source.
128
     *
129
     * @return string  The source.
130
     */
131 31
    public function getSource()
132
    {
133 31
        return $this->source;
134
    }
135
136
    /**
137
     * Get destination.
138
     *
139
     * @return string  The destination.
140
     */
141 12
    public function getDestination()
142
    {
143 12
        return $this->destination;
144
    }
145
146
    /**
147
     * Set destination.
148
     *
149
     * @param   string  $destination         path to destination
150
     * @return string  The destination.
151
     */
152 1
    public function setDestination($destination)
153
    {
154 1
        $this->destination = $destination;
155 1
    }
156
157
158
    /**
159
     *  Get converter name for display (defaults to the class name (short)).
160
     *
161
     *  Converters can override this.
162
     *
163
     * @return string  A display name, ie "Gd"
164
     */
165 31
    protected static function getConverterDisplayName()
166
    {
167
        // https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name/25308464
168 31
        return substr(strrchr('\\' . static::class, '\\'), 1);
169
    }
170
171
172
    /**
173
     *  Get converter id (defaults to the class name lowercased)
174
     *
175
     *  Converters can override this.
176
     *
177
     * @return string  A display name, ie "Gd"
178
     */
179 31
    protected static function getConverterId()
180
    {
181 31
        return strtolower(self::getConverterDisplayName());
182
    }
183
184
185
    /**
186
     * Create an instance of this class
187
     *
188
     * @param  string  $source       The path to the file to convert
189
     * @param  string  $destination  The path to save the converted file to
190
     * @param  array   $options      (optional)
191
     * @param  \WebPConvert\Loggers\BaseLogger   $logger       (optional)
192
     *
193
     * @return static
194
     */
195 20
    public static function createInstance($source, $destination, $options = [], $logger = null)
196
    {
197 20
        return new static($source, $destination, $options, $logger);
198
    }
199
200
201
    /**
202
     * Run conversion.
203
     *
204
     * @return void
205
     */
206 12
    private function doConvertImplementation()
207
    {
208 12
        $beginTime = microtime(true);
209
210 12
        if ($this->options['log-call-arguments']) {
211
            $this->logOptions();
212
            $this->logLn('');
213
        }
214
215 12
        $this->activateWarningLogger();
216
217 12
        $this->checkOptions();
218
219
        // Prepare destination folder
220 12
        $this->createWritableDestinationFolder();
221 11
        $this->removeExistingDestinationIfExists();
222
223 11
        if (!isset($this->options['_skip_input_check'])) {
224
            // Run basic input validations (if source exists and if file extension is valid)
225 11
            $this->checkSourceExists();
226 11
            $this->checkSourceMimeType();
227
228
            // Check that a file can be written to destination
229 11
            $this->checkDestinationWritable();
230
        }
231
232 11
        $this->checkOperationality();
233 6
        $this->checkConvertability();
234 6
        $this->runActualConvert();
235
236 3
        $source = $this->source;
237 3
        $destination = $this->destination;
238
239 3
        if (!@file_exists($destination)) {
240
            throw new ConversionFailedException('Destination file is not there: ' . $destination);
241 3
        } elseif (@filesize($destination) === 0) {
242
            unlink($destination);
243
            throw new ConversionFailedException('Destination file was completely empty');
244
        } else {
245 3
            if (!isset($this->options['_suppress_success_message'])) {
246 3
                $this->ln();
247
                $msg = 'Converted image in ' .
248 3
                    round((microtime(true) - $beginTime) * 1000) . ' ms';
249
250 3
                $sourceSize = @filesize($source);
251 3
                if ($sourceSize !== false) {
252
                    $msg .= ', reducing file size with ' .
253 3
                        round((filesize($source) - filesize($destination))/filesize($source) * 100) . '% ';
254
255 3
                    if ($sourceSize < 10000) {
256 1
                        $msg .= '(went from ' . round(filesize($source)) . ' bytes to ';
257 1
                        $msg .= round(filesize($destination)) . ' bytes)';
258
                    } else {
259 3
                        $msg .= '(went from ' . round(filesize($source)/1024) . ' kb to ';
260 3
                        $msg .= round(filesize($destination)/1024) . ' kb)';
261
                    }
262
                }
263 3
                $this->logLn($msg);
264
            }
265
        }
266
267 3
        $this->deactivateWarningLogger();
268 3
    }
269
270
    //private function logEx
271
    /**
272
     * Start conversion.
273
     *
274
     * Usually you would rather call the static convert method, but alternatively you can call
275
     * call ::createInstance to get an instance and then ::doConvert().
276
     *
277
     * @return void
278
     */
279 12
    public function doConvert()
280
    {
281
        try {
282
            //trigger_error('hello', E_USER_ERROR);
283 12
            $this->doConvertImplementation();
284 9
        } catch (WebPConvertException $e) {
285 9
            $this->logLn('');
286
            /*
287
            if (isset($e->description) && ($e->description != '')) {
288
                $this->log('Error: ' . $e->description . '. ', 'bold');
289
            } else {
290
                $this->log('Error: ', 'bold');
291
            }
292
            */
293 9
            $this->log('Error: ', 'bold');
294 9
            $this->logLn($e->getMessage(), 'bold');
295 9
            throw $e;
296
        } catch (\Exception $e) {
297
            $className = get_class($e);
298
299
            $classNameParts = explode("\\", $className);
300
            $shortClassName = array_pop($classNameParts);
301
302
            $this->logLn('');
303
            $this->logLn($shortClassName . ' thrown in ' . $e->getFile() . ':' . $e->getLine(), 'bold');
304
            $this->logLn('Message: "' . $e->getMessage() . '"', 'bold');
305
            //$this->logLn('Exception class: ' . $className);
306
307
            $this->logLn('Trace:');
308
            foreach ($e->getTrace() as $trace) {
309
                //$this->logLn(print_r($trace, true));
310
                $this->logLn(
311
                    $trace['file'] . ':' . $trace['line']
312
                );
313
            }
314
            throw $e;
315
        } /*catch (\Error $e) {
316
            $this->logLn('ERROR');
317
        }*/
318 3
    }
319
320
    /**
321
     * Runs the actual conversion (after setup and checks)
322
     * Simply calls the doActualConvert() of the actual converter.
323
     * However, in the EncodingAutoTrait, this method is overridden to make two conversions
324
     * and select the smallest.
325
     *
326
     * @return void
327
     */
328 4
    protected function runActualConvert()
329
    {
330 4
        $this->doActualConvert();
331 3
    }
332
333
    /**
334
     * Convert an image to webp.
335
     *
336
     * @param   string  $source              path to source file
337
     * @param   string  $destination         path to destination
338
     * @param   array   $options (optional)  options for conversion
339
     * @param   BaseLogger $logger (optional)
340
     *
341
     * @throws  ConversionFailedException   in case conversion fails in an antipiciated way
342
     * @throws  \Exception   in case conversion fails in an unantipiciated way
343
     * @return  void
344
     */
345 13
    public static function convert($source, $destination, $options = [], $logger = null)
346
    {
347 13
        $c = self::createInstance($source, $destination, $options, $logger);
348
349 12
        $c->logLn(self::getConverterDisplayName() . ' converter ignited');
350
351 12
        $c->doConvert();
352
        //echo $instance->id;
353 3
    }
354
355
    /**
356
     * Get mime type for image (best guess).
357
     *
358
     * It falls back to using file extension. If that fails too, false is returned
359
     *
360
     * PS: Is it a security risk to fall back on file extension?
361
     * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
362
     * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
363
     *
364
     * @return  string|false|null mimetype (if it is an image, and type could be determined / guessed),
365
     *    false (if it is not an image type that the server knowns about)
366
     *    or null (if nothing can be determined)
367
     */
368 31
    public function getMimeTypeOfSource()
369
    {
370 31
        if (!isset($this->sourceMimeType)) {
371 31
            $this->sourceMimeType = ImageMimeTypeGuesser::lenientGuess($this->source);
372
        }
373 31
        return $this->sourceMimeType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->sourceMimeType also could return the type boolean which is incompatible with the documented return type false|null|string.
Loading history...
374
    }
375
}
376