Completed
Push — master ( 61186c...642a22 )
by Bjørn
07:55 queued 05:48
created

Stack::getClassNameOfConverter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 10
c 0
b 0
f 0
1
<?php
2
3
// TODO: Quality option
4
5
namespace WebPConvert\Convert\Converters;
6
7
use WebPConvert\Convert\Converters\AbstractConverter;
8
use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\ConverterNotFoundException;
9
use WebPConvert\Convert\Exceptions\ConversionFailedException;
10
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
11
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
12
use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
13
14
//use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
15
16
/**
17
 * Convert images to webp by trying a stack of converters until success.
18
 *
19
 * @package    WebPConvert
20
 * @author     Bjørn Rosell <[email protected]>
21
 * @since      Class available since Release 2.0.0
22
 */
23
class Stack extends AbstractConverter
24
{
25
26 5
    protected function getOptionDefinitionsExtra()
27
    {
28
        return [
29
            [
30 5
                'converters',
31
                'array', [
32
                    'cwebp', 'vips', 'wpc', 'imagickbinary', 'ewww', 'imagick', 'gmagick', 'gmagickbinary', 'gd'
33
                ],
34
                true
35
            ],
36
            ['shuffle', 'boolean', false],
37
            ['preferred-converters', 'array', []],
38
            ['extra-converters', 'array', []]
39
        ];
40
    }
41
42
    public static $availableConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary', 'wpc', 'ewww'];
43
    public static $localConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary', 'gmagickbinary'];
44
45 1
    public static function converterIdToClassname($converterId)
46
    {
47
        switch ($converterId) {
48 1
            case 'imagickbinary':
49
                $classNameShort = 'ImagickBinary';
50
                break;
51 1
            case 'gmagickbinary':
52
                $classNameShort = 'GmagickBinary';
53
                break;
54
            default:
55 1
                $classNameShort = ucfirst($converterId);
56
        }
57 1
        $className = 'WebPConvert\\Convert\\Converters\\' . $classNameShort;
58 1
        if (is_callable([$className, 'convert'])) {
59
            return $className;
60
        } else {
61 1
            throw new ConverterNotFoundException('There is no converter with id:' . $converterId);
62
        }
63
    }
64
65 2
    public static function getClassNameOfConverter($converterId)
66
    {
67 2
        if (strtolower($converterId) == $converterId) {
68 1
            return self::converterIdToClassname($converterId);
69
        }
70 1
        $className = $converterId;
71 1
        if (!is_callable([$className, 'convert'])) {
72
            throw new ConverterNotFoundException('There is no converter with class name:' . $className);
73
        }
74
75 1
        return $className;
76
    }
77
78
    /**
79
     * Check (general) operationality of imagack converter executable
80
     *
81
     * @throws SystemRequirementsNotMetException  if system requirements are not met
82
     */
83 3
    public function checkOperationality()
84
    {
85 3
        if (count($this->options['converters']) == 0) {
86 1
            throw new ConverterNotOperationalException(
87 1
                'Converter stack is empty! - no converters to try, no conversion can be made!'
88
            );
89
        }
90
91
        // TODO: We should test if all converters are found in order to detect problems early
92
93 2
        $this->logLn('Stack converter ignited');
94 2
    }
95
96 2
    protected function doActualConvert()
97
    {
98 2
        $options = $this->options;
99
100 2
        $beginTimeStack = microtime(true);
101
102
103
        // If we have set converter options for a converter, which is not in the converter array,
104
        // then we add it to the array
105
        /*
106
        if (isset($options['converter-options'])) {
107
            foreach ($options['converter-options'] as $converterName => $converterOptions) {
108
                if (!in_array($converterName, $converters)) {
109
                    $converters[] = $converterName;
110
                }
111
            }
112
        }*/
113
114
        //$this->logLn('converters: ' . print_r($converters, true));
115
116 2
        $defaultConverterOptions = $options;
117
118 2
        unset($defaultConverterOptions['converters']);
119 2
        unset($defaultConverterOptions['converter-options']);
120 2
        $defaultConverterOptions['_skip_input_check'] = true;
121 2
        $defaultConverterOptions['_suppress_success_message'] = true;
122
123 2
        $anyRuntimeErrors = false;
124
125 2
        $converters = $options['converters'];
126 2
        $this->logLn(print_r($converters));
127 2
        if (count($options['extra-converters']) > 0) {
128
            $converters = array_merge($converters, $options['extra-converters']);
129
        }
130
131
        // preferred-converters
132 2
        if (count($options['preferred-converters']) > 0) {
133
            foreach (array_reverse($options['preferred-converters']) as $prioritizedConverter) {
134
                foreach ($converters as $i => $converter) {
135
                    if (is_array($converter)) {
136
                        $converterId = $converter['converter'];
137
                    } else {
138
                        $converterId = $converter;
139
                    }
140
                    if ($converterId == $prioritizedConverter) {
141
                        //$this->logLn($i . ':' . $prioritizedConverter);
142
                        unset($converters[$i]);
143
                        array_unshift($converters, $converter);
144
                        break;
145
                    }
146
                }
147
            }
148
            // perhaps write the order to the log? (without options) - but this requires some effort
149
        }
150
151
        // shuffle
152 2
        if ($options['shuffle']) {
153
            shuffle($converters);
154
        }
155
156 2
        $this->logLn(print_r($converters));
157 2
        foreach ($converters as $converter) {
158 2
            if (is_array($converter)) {
159
                $converterId = $converter['converter'];
160
                $converterOptions = $converter['options'];
161
            } else {
162 2
                $converterId = $converter;
163 2
                $converterOptions = [];
164 2
                if (isset($options['converter-options'][$converterId])) {
165
                    // Note: right now, converter-options are not meant to be used,
166
                    //       when you have several converters of the same type
167
                    $converterOptions = $options['converter-options'][$converterId];
168
                }
169
            }
170
171 2
            $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
172
173 2
            $beginTime = microtime(true);
174
175 2
            $className = self::getClassNameOfConverter($converterId);
176
177
178
            try {
179 1
                $converterDisplayName = call_user_func(
180 1
                    [$className, 'getConverterDisplayName']
181
                );
182
            } catch (\Exception $e) {
183
                // TODO: handle failure better than this
184
                $converterDisplayName = 'Untitled converter';
185
            }
186
187
            try {
188 1
                $this->ln();
189 1
                $this->logLn('Trying: ' . $converterId, 'italic');
190
191 1
                call_user_func(
192 1
                    [$className, 'convert'],
193 1
                    $this->source,
194 1
                    $this->destination,
195 1
                    $converterOptions,
196 1
                    $this->logger
197
                );
198
199
                //self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger);
200
201 1
                $this->logLn($converterDisplayName . ' succeeded :)');
202 1
                return;
203
            } catch (ConverterNotOperationalException $e) {
204
                $this->logLn($e->getMessage());
205
            } catch (ConversionFailedException $e) {
206
                $this->logLn($e->getMessage(), 'italic');
207
                $prev = $e->getPrevious();
208
                if (!is_null($prev)) {
209
                    $this->logLn($prev->getMessage(), 'italic');
210
                    $this->logLn(' in ' . $prev->getFile() . ', line ' . $prev->getLine(), 'italic');
211
                    $this->ln();
212
                }
213
                //$this->logLn($e->getTraceAsString());
214
                $anyRuntimeErrors = true;
215
            } catch (ConversionSkippedException $e) {
216
                $this->logLn($e->getMessage());
217
            }
218
219
            $this->logLn($converterDisplayName . ' failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
220
        }
221
222
        $this->ln();
223
        $this->logLn('Stack failed in ' . round((microtime(true) - $beginTimeStack) * 1000) . ' ms');
224
225
        if ($anyRuntimeErrors) {
0 ignored issues
show
introduced by
The condition $anyRuntimeErrors is always false.
Loading history...
226
            // At least one converter failed
227
            throw new ConversionFailedException(
228
                'None of the converters in the stack could convert the image. ' .
229
                'At least one failed, even though its requirements seemed to be met.'
230
            );
231
        } else {
232
            // All converters threw a SystemRequirementsNotMetException
233
            throw new ConverterNotOperationalException('None of the converters in the stack are operational');
234
        }
235
    }
236
}
237